Javascript 零基础教程

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 是一个函数声明。它接受两个参数 lengthwidth,并返回它们的乘积,即矩形的面积。

2. 函数表达式 (Function Expressions)

函数表达式提供了在 JavaScript 中定义函数的另一种方法。在这种方法中,函数被定义为一个更大表达式的一部分,通常是一个赋值表达式。

2.1 语法和结构

函数表达式的基本语法如下:

let functionName = function(parameter1, parameter2, ...) {
  // 函数体 - 要执行的代码
  return value; // 可选的 return 语句
};
  • let functionName =: 这将函数赋值给一个变量。使用 let(或 constvar)关键字来声明变量。
  • 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 数组中的每个元素。