JavaScript 函数声明和函数表达式
函数声明(Function Declarations)和函数表达式(Function Expressions)是 JavaScript 中定义函数的两种方式。虽然它们殊途同归——都是为了创建一个可重用的代码块——但它们在语法上,更重要的是在代码执行期间的处理方式上,存在着显著差异。
理解这些差异对于编写整洁、高效且可预测的 JavaScript 代码至关重要。本章节将深入探讨这两种方法的细微差别,强调它们的独特特征,并演示在何时该优先选择哪一种。
1. 函数声明 (Function Declarations)
函数声明是 JavaScript 中定义函数最常见、最直接的方式。它的特征是使用 function 关键字,后跟函数名称、括号内的参数列表,以及花括号内的函数体。
1.1 语法和结构
函数声明的基本语法如下:
function functionName(parameter1, parameter2, ...) {
// 函数体 - 要执行的代码
return value; // 可选的 return 语句
}- function 关键字: 这个关键字标志着函数声明的开始。
- functionName: 这是用于调用或引用函数的标识符(名字)。
- parameter1, parameter2...: 这些是函数接受的输入值(参数)。它们是可选的;一个函数可以没有参数。
- { ... }: 花括号包裹着函数体,其中包含调用函数时要执行的 JavaScript 语句。
- return value: 该语句指定函数将返回的值。如果没有
return语句,函数将隐式返回undefined。
1.2 提升 (Hoisting)
函数声明的一个关键特征是它们会被提升(hoisted)。这意味着 JavaScript 解释器会在执行代码之前,将函数的声明移动到其作用域的顶部,无论它们实际出现在代码的哪个位置。
因此,你可以在代码中实际声明函数之前调用该函数。
// 在声明之前调用函数
console.log(add(5, 3)); // 输出: 8
// 函数声明
function add(a, b) {
return a + b;
}在这个例子中,即使 add 函数是在调用之后才声明的,代码也能无错执行,因为函数声明被提升到了作用域的顶部。
1.3 示例:计算矩形面积
// 用于计算矩形面积的函数声明
function calculateRectangleArea(length, width) {
return length * width;
}
// 调用函数并存储结果
let area = calculateRectangleArea(10, 5);
console.log("矩形的面积是: " + area); // 输出: 矩形的面积是: 50在这个例子中,calculateRectangleArea 是一个函数声明。它接受两个参数 length 和 width,并返回它们的乘积,即矩形的面积。
2. 函数表达式 (Function Expressions)
函数表达式提供了在 JavaScript 中定义函数的另一种方法。在这种方法中,函数被定义为一个更大表达式的一部分,通常是一个赋值表达式。
2.1 语法和结构
函数表达式的基本语法如下:
let functionName = function(parameter1, parameter2, ...) {
// 函数体 - 要执行的代码
return value; // 可选的 return 语句
};- let functionName =: 这将函数赋值给一个变量。使用
let(或const、var)关键字来声明变量。 - function(parameter1, parameter2, ...): 这是一个匿名函数。之所以叫“匿名”,是因为它在
function关键字后面没有名字(虽然命名函数表达式也是可能的,我们稍后会看到)。 - { ... }: 花括号包裹着函数体,包含要执行的 JavaScript 语句。
- return value: 该语句指定函数将返回的值。
2.2 提升(或缺乏提升)
与函数声明不同,函数表达式不会被提升。这意味着你不能在代码中实际定义函数表达式之前调用它。尝试这样做会导致 ReferenceError(引用错误)。
// 在定义之前调用函数
// console.log(multiply(4, 6)); // 这将导致 ReferenceError: Cannot access 'multiply' before initialization
// 函数表达式
let multiply = function(a, b) {
return a * b;
};
console.log(multiply(4, 6)); // 输出: 24在这个例子中,如果你取消注释函数表达式之前的 console.log(multiply(4, 6)); 这行代码,你会遇到 ReferenceError,因为在代码执行时,multiply 变量尚未初始化。
2.3 匿名与命名函数表达式
函数表达式可以是匿名的,也可以是命名的。
匿名函数表达式是指在 function 关键字后没有名字的函数,如前面的例子所示。
另一方面,命名函数表达式拥有一个名字。
// 命名函数表达式
let factorial = function calculateFactorial(n) {
if (n <= 1) {
return 1;
} else {
return n * calculateFactorial(n - 1); // 使用函数名进行递归调用
}
};
console.log(factorial(5)); // 输出: 120在这个例子中,calculateFactorial 是函数表达式的名字。虽然函数被赋值给了 factorial 变量,但函数本身有一个名字,可用于递归或调试。名字 calculateFactorial 只能在函数内部访问。
2.4 示例:计算数字的平方
// 用于计算数字平方的函数表达式
let square = function(number) {
return number * number;
};
// 调用函数并存储结果
let squaredValue = square(7);
console.log("7 的平方是: " + squaredValue); // 输出: 7 的平方是: 49这里,square 是一个赋值给变量 square 的函数表达式。它接受一个参数 number 并返回它的平方。
3. 关键区别总结
| 特性 | 函数声明 (Function Declaration) | 函数表达式 (Function Expression) |
|---|---|---|
| 语法 | function functionName(params) { ... } | let functionName = function(params) { ... }; |
| 提升 (Hoisting) | 被提升 (可以在声明之前调用) | 不被提升 (必须在定义之后调用) |
| 命名 | 函数名是强制的 | 可以是匿名的或命名的 |
| 使用场景 | 通用函数,顶层函数 | 将函数赋值给变量,回调函数,闭包 |
4. 实际考量与使用场景
4.1 在声明和表达式之间做选择
选择函数声明还是函数表达式,通常取决于具体的上下文和代码风格偏好。
- 函数声明: 当你需要定义一个可以在作用域内任何地方调用的函数时(无论它在代码中的位置如何),请使用函数声明。这通常适用于在整个脚本中使用的通用函数。
- 函数表达式: 当你需要将函数赋值给变量,将其作为参数传递给另一个函数(作为回调),或者创建闭包时,请使用函数表达式。当你想要控制函数的作用域和生命周期时,函数表达式也很有用。
4.2 立即调用函数表达式 (IIFE)
虽然 IIFE 会在下一课中详细介绍,但这里值得简要提及,因为它们总是使用函数表达式创建的。IIFE 是定义后立即执行的函数表达式。它们通常用于创建一个新的作用域,以避免变量提升问题。
(function() {
let message = "来自 IIFE 的问候!";
console.log(message); // 输出: 来自 IIFE 的问候!
})();4.3 回调函数 (Callbacks)
在异步操作或使用数组方法时,函数表达式经常被用作回调函数。
let numbers = [1, 2, 3, 4, 5];
// 使用函数表达式作为 map 方法的回调
let squaredNumbers = numbers.map(function(number) {
return number * number;
});
console.log(squaredNumbers); // 输出: [1, 4, 9, 16, 25]在这个例子中,匿名函数表达式被用作 map 方法的回调,该方法将函数应用于 numbers 数组中的每个元素。