在JavaScript中,function
关键字做一个简单的工作:创建一个函数。但是,使用关键字定义函数的方式可以创建具有不同属性的函数。
在这篇文章中,你将了解如何使用function
关键字来编写函数声明和函数表达式,以及这两种类型的函数之间有什么区别。
1. 函数表达式 VS 函数声明
函数声明和函数表达式是使用Function关键字创建函数的两种方法。
让我们举个例子来说明两者的区别——我们先创建两个版本的求和
函数:
function sumA(a, b) { return a + b; } (function sumB(a, b) { return a + b; }); sumA(1, 2); // ??? sumB(1, 2); // ???
在一种情况下,可以像往常一样定义函数(如:sumA()
)。在另一种情况下,函数被放置在一对括号中(如:sumB()
)。
如果你调用sumA(1,2)
和sumB(1,2)
会发生什么?
正如预期的那样,sumA(1, 2)
只是返回1
和2
个数字的和3
。然而,调用sumB(1, 2)
抛出一个未捕获的ReferenceError: sumB
未定义。
原因是,sumA
是使用函数声明创建
的,该函数声明在当前作用域中创建了一个函数变量(与函数名同名)。但是sumB
是使用函数表达式
创建的(它被括在圆括号中),它不会在当前作用域内创建函数变量。
如果你想访问使用函数表达式
创建的函数,那么将函数对象保存到一个变量中:
// Works! const sum = (function sumB(a, b) { return a + b; }); sum(1, 2); // => 3
下面是一个关于如何区分函数声明和函数表达式的简单提示:
如果语句以function关键字开头,那么它就是一个函数声明,否则就是一个函数表达式。
// 函数声明: 以 `function` 关键字开头 function sumA(a, b) { return a + b; } // 函数表达式: 不以 `function` 关键字开头 const mySum = (function sumB(a, b) { return a + b; }); // 函数表达式: 不以 `function` 关键字开头 [1, 2, 3].reduce(function sum3(acc, number) { return acc + number });
从更高的角度来看,函数声明对于创建独立的函数
很有用,但是函数表达式作为回调
很好。
现在,让我们深入了解函数声明和函数表达式的行为。
2. 函数声明
正如你在前面的例子中已经看到的,sumA
是一个函数声明:
// 函数声明 function sumA(a, b) { return a + b; } sumA(4, 5); // => 9
当一个语句包含function
关键字,后面跟着函数名、一对带有参数(param1, param2, paramN)的圆括号以及用一对**大括号{}**括起来的函数体时,就会发生函数声明。
函数声明创建了一个函数变量——**一个与函数名相同的变量(**例如前面例子中的sumA
)。函数变量可以在当前作用域(在函数声明之前和之后),甚至在函数作用域本身内访问。
函数变量通常用于调用函数或将函数对象传递给其他函数(高阶函数)。
例如,我们写一个函数sumArray(array)
,它对数组(数组可以包含数字或其他数组)中的项进行递归求和:
sumArray([10, [1, [5]]]); // => 16 function sumArray(array) { let sum = 0; for (const item of array) { sum += Array.isArray(item) ? sumArray(item) : item; } return sum; } sumArray([1, [4, 6]]); // => 11
函数sumArray(array){…}
是一个函数声明。
包含函数对象的函数变量sumArray
在当前范围内可用:在 sumArray([10,[1,[5]]])
之前和在sumArray([1,[4, 6]])
之后的函数声明,以及在函数本身的范围内sumArray(item)
(允许递归调用)。
由于变量提升,函数变量在函数声明之前可用。
2.1 函数声明的注意事项
函数声明语法的作用是创建独立的函数。函数声明应该在全局作用域内或直接在其他函数的作用域内:
// Good! function myFunc1(param1, param2) { return param1 + param2; } function bigFunction(param) { // Good! function myFunc2(param1, param2) { return param1 + param2; } const result = myFunc2(1, 3); return result + param; }
出于同样的原因,不建议在条件语句(if
)和循环语句(while, for
)中使用函数声明:
// Bad! if (myCondition) { function myFunction(a, b) { return a * b; } } else { function myFunction(a, b) { return a + b; } } myFunction(2, 3);
而使用函数表达式可以更好地在条件语句中创建函数。
3. 函数表达式
当function
关键字在表达式中创建一个函数(有或没有名称)时,才是一个函数表达式。
以下是使用表达式创建的函数示例:
// 函数表达式 const sum = (function sumB(a, b) { return a + b; }); const myObject = { myMethod: function() { return 42; } }; const numbers = [4, 1, 6]; numbers.forEach(function callback(number) { console.log(number); // logs 4 // logs 1 // logs 1 });
在函数表达式中创建的函数有两种:
- 如果表达式内的函数没有名称,例如
function() {return 42}
,则这是一个匿名函数表达式; - 如果函数有名字,例如前面例子中的
sumB
和callback
,那么这就是一个命名函数表达式。
3.1 函数表达式的注意事项
函数表达式适合作为回调函数或由条件创建的函数:
// 由条件创建的函数 let callback; if (true) { callback = function() { return 42 }; } else { callback = function() { return 3.14 }; } // 作为回调函数使用 [1, 2, 3].map(function increment(number) { return number + 1; }); // => [2, 3, 4]
如果你已经创建了一个命名函数表达式,请注意函数变量只在已创建函数范围内可用:
const numbers = [4]; numbers.forEach(function callback(number) { console.log(callback); // logs function() { ... } }); console.log(callback); // ReferenceError: callback is not defined
Callback
是一个命名函数表达式,因此Callback
函数变量只能在Callback()
函数范围内使用,而不能在Callback()
函数范围外使用。
但是,如果你将函数对象存储到一个常规变量中,那么你可以在函数作用域内外从该变量访问函数对象:
const callback = function(number) { console.log(callback); // logs function() { ... } }; const numbers = [4]; numbers.forEach(callback); console.log(callback); // logs function() { ... }
4. 总结
根据如何使用function
关键字创建函数,可以以两种方式来创建函数:函数声明和函数表达式。
函数声明发生在以function关键字开始的语句中:
// 函数声明 function sumA(a, b) { return a + b; }
函数声明对于创建独立的、通用的函数非常有用。
但是,如果一个语句不以function
关键字开头,那么就有一个函数表达式:
// 函数表达式 (function sumB(a, b) { return a + b; });
使用函数表达式创建的函数对于根据条件创建回调函数或函数很有用。
最后,考考大家:
function sum(a, b) { return a + b } + 1;
是函数声明还是函数表达式,留言写下你的答案!