JavaScript
JavaScript 函数表达式与箭头函数
函数表达式(Function Expression)
function 关键字可以在一个表达式中用来定义一个函数。
你可以使用函数表达式来创建一个匿名函数(anonymous function)。一个函数表达式可以被用作一个 IIFE(立即执行函数表达式),它一旦定义就运行。
1. 语法
基本的函数表达式语法如下:
let function_expression = function [name]([param1[, param2[, ..., paramN]]]) {
statements
};
name: 函数名称。可以省略,此时函数是匿名的。paramN: 传递给函数的参数名称。statements: 构成函数体的语句。
2. 函数表达式与函数声明的区别
函数表达式与函数声明非常相似,并且具有几乎相同的语法。它们之间的主要区别在于函数名称,在函数表达式中可以省略该名称以创建匿名函数。
下面的例子展示了函数表达式和函数声明的不同:
// 函数声明
function add(a, b) {
return a + b;
}
// 函数表达式 (匿名)
const add = function(a, b) {
return a + b;
};
3. 函数表达式提升 (Hoisting)
JavaScript 中的函数表达式不会被提升,这与函数声明不同。你不能在创建函数表达式之前使用它。
请看下面这个例子:
notHoisted(); // TypeError: notHoisted is not a function
var notHoisted = function() {
console.log("bar");
};
在上面的代码中,notHoisted 变量被提升了,但在赋值之前,它的值是 undefined,因此调用它会导致 TypeError。这与函数声明不同,函数声明会被整体提升,因此可以在声明之前被调用。
4. 具名函数表达式 (Named Function Expression)
如果你想在函数体内部引用当前函数,则需要创建一个具名函数表达式。函数名称将会(且只会)作为函数体的本地变量。
const factorial = function fac(n) {
return n < 2 ? 1 : n * fac(n - 1);
};
console.log(factorial(3)); // 输出 6
在上面的例子中,递归函数 fac 的名称在函数体内部是可见的,但在外部作用域是不可见的。
5. 立即执行函数表达式 (IIFE)
函数表达式可以被用作一个IIFE,它一旦定义就立即运行。这种模式常用于创建一个独立的作用域,以避免变量污染全局作用域。
(function() {
console.log("This runs immediately!");
})();
6. 函数表达式中的 this
与箭头函数不同,传统的函数表达式(包括匿名函数)拥有自己的 this 绑定。this 的值取决于函数被调用的方式,而不是它被定义的地方。例如,当函数作为对象的方法被调用时,this 会指向该对象。
7. 使用场景
函数表达式非常灵活,适用于多种场景:
- 作为值的传递:将函数作为参数传递给其他函数(回调函数)。
- 条件创建:根据条件在运行时动态创建并定义函数。
- 立即执行:使用 IIFE 模式创建独立作用域,封装私有变量。
💎 总结
总的来说,函数表达式是 JavaScript 中一个核心且强大的特性:
- 核心概念:函数表达式是指在表达式中定义函数的方式,可以将函数作为值赋给变量或属性。
- 关键区别:与函数声明不同,函数表达式不会被提升;并且,它可以省略函数名,创建匿名函数。
- 主要用途:它被广泛用于创建回调函数、实现递归的具名函数表达式,以及封装私有变量的立即执行函数表达式(IIFE)。
- 实践建议:函数表达式为开发者提供了在运行时动态创建和使用函数的灵活性,是现代 JavaScript 开发(尤其是异步编程和模块化开发)中不可或缺的工具。
揭秘 JavaScript 箭头函数
原文作者:Ajo John
译者:子舒
箭头函数是函数表达式的替代方法,但在语法上有所不同,不能在所有情况下使用。
现在,让我们试着从语法、执行、作用域和提升以及代码示例方面来理解箭头函数。
1. 语法
const arrowFunctionSyntax = () => {
console.log('Hi, I am an arrow function');
};
arrowFunctionSyntax();
在上面的代码示例中,我们可以看到箭头函数类似于函数表达式,因为它们被赋值为变量。主要区别在于函数的编写方式。
以下是我们可以根据其语法从上述代码中得出的观察结果:
- 它不包含
function关键字。 - 它没有函数名,这意味着这些是匿名函数。
- 引入了箭头
=>符号。
const arrowFunctionWithOneParam = (number) => number + 1;
const arrowFunctionWithMultipleParams = (numberOne, numberTwo) => {
let sum = numberOne + numberTwo;
return sum;
};
console.log(arrowFunctionWithOneParam(5));
console.log(arrowFunctionWithMultipleParams(5, 6));
如果我们同时观察 arrowFunctionWithOneParam 和 arrowFunctionWithMultipleParams,我们可以发现三者的不同——括号 ()、块 {} 和 return 关键字的使用。
根据箭头函数的语法,如果函数只接受一个参数,可以忽略括号 ()。如果函数只包含一条语句,则可以忽略块 {},最后其实也可以忽略 return,如果函数只包含一个语句。
2. 执行
const arrowFunctionExecution = () => {
console.log('Hi, my execution is similar to normal function');
};
arrowFunctionExecution();
在执行箭头函数时,它们的执行方式与其他普通函数的执行方式相似。当 JS 引擎执行 arrowFunctionExecution() 时,它会创建一个函数,执行上下文并被推送到调用堆栈。一旦创建了执行上下文,它就会启动创建阶段。在这个阶段,它将创建参数对象并在其本地内存堆中声明所有变量。
与普通函数相比,主要区别在于 this 的声明。箭头函数没有自己的 this 变量;this 在箭头函数中使用时会得到词法解析。
在创建阶段之后不久,执行阶段开始。这时它开始执行语句 console.log(),并将 "Hi, my execution is similar to normal function" 打印到控制台。
3. 作用域
const arrowFunctionScope = () => {
console.log('Hi, my scoping rules works similar to function expression');
};
这些函数遵循与其他函数表达式相同的作用域规则。这些函数有自己的作用域,函数内部声明的任何变量都不能在函数外部访问。这些函数也不适用于 call、apply 和 bind 方法,这些方法通常依赖于作用域。
4. 提升
amIGoingToBeHoisted();
var amIGoingToBeHoisted = () => {
console.log('The answer is NO');
};
箭头函数没有被提升,因为它们也是函数表达式,其中函数被分配为变量的值。当 JavaScript 引擎执行上述代码时,在创建阶段,声明语句 var amIGoingToBeHoisted 将被移到顶部并使用值 undefined 进行初始化,而将初始值留在后面。在执行阶段,遇到语句 amIGoingToBeHoisted 时会抛出类型错误,因为它的值是 undefined,这不是提升后的函数类型。
5. this
普通函数中的 this
在 JavaScript 中,
this关键字指的是它所属的对象。
const myObject = {
myMethod(items) {
console.log(this); // myObject 对象
const callback = function() {
console.log(this); // 全局对象(或严格模式下的 undefined)
};
items.forEach(callback);
},
};
myObject.myMethod([1, 2, 3]);
在上面的示例中,this 在回调函数内部丢失了与 myObject 的关联,因为回调函数有自己的 this 绑定。
箭头函数中的 this
const myObject = {
myMethod(items) {
console.log(this); // myObject 对象
const callback = () => {
console.log(this); // myObject 对象
};
items.forEach(callback);
},
};
myObject.myMethod([1, 2, 3]);
由于箭头函数没有自己的 this 绑定,它们会从封闭作用域继承 this 的值。这解决了普通函数中 this 丢失的问题。
6. 不能用作构造函数
箭头函数不能用作构造函数。与普通函数不同,不能使用 new 关键字调用箭头函数。如果尝试这样做,会引发错误。
const ArrowFunction = () => {};
const arrowFunctionInstance = new ArrowFunction(); // TypeError: ArrowFunction is not a constructor
7. 没有 arguments 对象
箭头函数没有自己的 arguments 对象。在箭头函数内部访问 arguments 将解析为封闭作用域中的 arguments 绑定。
function regularFunction() {
const arrowFunction = () => {
console.log(arguments); // 外层函数的 arguments 对象
};
arrowFunction();
}
regularFunction(1, 2, 3);
8. 何时使用箭头函数
适合使用箭头函数的场景:
- 数组方法(
map、filter、reduce等)的回调函数 - 需要保留
this上下文的事件处理程序 - Promise 链(
then、catch) - 需要简洁语法的简单函数
不适合使用箭头函数的场景:
- 作为对象的方法(因为会丢失
this绑定) - 需要使用
arguments对象时 - 需要动态
this绑定的场景 - 需要作为构造函数时
9. 总结
箭头函数为 JavaScript 提供了一种更简洁的函数写法,但在语义上存在重要差异:
- 更简洁的语法:省略了
function关键字,对于单语句函数可以省略return和大括号。 - 词法
this绑定:这是箭头函数最重要的特性,它从封闭作用域继承this,而不是绑定自己的this。 - 不能作为构造函数:箭头函数没有
[[Construct]]内部方法,不能与new一起使用。 - 没有
arguments对象:需要使用剩余参数(...args)作为替代。 - 没有原型:箭头函数没有
prototype属性。 - 不能被提升:作为函数表达式,箭头函数遵循变量的提升规则。
理解这些差异将帮助您在正确的场景中使用箭头函数,编写更清晰、更可预测的 JavaScript 代码。