人人都会AI编程

9.1 函数的多种定义形式与差异

更新时间:2026-07-11

JavaScript 中创建函数的方式不止一种,每种方式在语法、行为、提升规则以及 this 绑定上都有细微却重要的区别。理解这些差异,能帮助你根据场景选择最合适的形式,避免一些隐蔽的 bug。

9.1.1 函数声明(Function Declaration)

最常见的定义方式,使用 function 关键字,后面紧跟函数名。

function add(a, b) {
  return a + b;
}
  • 提升(Hoisting):函数声明会被整体提升到所在作用域的顶部,因此可以在声明之前调用。
  • 命名:必须有函数名,便于调试时在调用栈中显示。
  • this:取决于调用方式(动态绑定,见第 5 章)。
console.log(add(2, 3)); // 5,即使 add 还没“声明”,也能正常工作

由于提升特性,函数声明适合用于定义通用的工具函数,不必担心调用顺序。

9.1.2 函数表达式(Function Expression)

将函数赋值给一个变量,函数部分可以是匿名的。

const add = function(a, b) {
  return a + b;
};
  • 提升:只有变量声明(const add)会被提升,赋值操作仍留在原地。因此,在赋值之前调用会报错(暂时性死区)。
  • 可匿名:函数表达式右侧可以没有名称,但这会使调用栈辨识困难。也可以给它命名(命名函数表达式),如:
const factorial = function fact(n) {
  return n <= 1 ? 1 : n * fact(n - 1);
};

命名的好处是:函数内部可以通过这个名称引用自己(适合递归),而且这个名称只在该函数作用域内可见,不会污染外部。

  • 使用时机:适合将函数作为回调传递,或者需要在运行时条件性定义函数时(在条件分支内赋值)。

9.1.3 箭头函数(Arrow Function)

ES6 引入的简洁写法,使用 => 定义。

const add = (a, b) => a + b;
  • 语法简洁:如果函数体只有一条 return 语句,可以省略花括号和 return 关键字。
  • 没有自己的 this:箭头函数的 this 继承自外层词法作用域,无法被 callapplybind 改变(详见 5.4 节)。
  • 不能作为构造函数:不能使用 new 调用。
  • 没有 arguments 对象:但可以使用剩余参数 ...args 代替。
  • 没有 prototype 属性
  • 提升行为同普通变量声明const / let 声明的箭头函数也存在暂时性死区。

典型适用场景:需要保持外层 this 的回调,比如在类的方法内部使用 setTimeout 或事件监听器。

class Timer {
  constructor() {
    this.seconds = 0;
    setInterval(() => {
      this.seconds++; // 这里的 this 指向 Timer 实例
    }, 1000);
  }
}

如果使用普通函数,this 会指向 window(或严格模式下 undefined),需要用 bind 额外绑定,箭头函数则天然解决了这个问题。

9.1.4 构造函数(Function 构造函数)

通过 new Function() 动态创建函数,所有参数都是字符串形式。

const add = new Function('a', 'b', 'return a + b;');
  • 作用域:这种函数总是在全局作用域创建,无法访问局部作用域的变量。
  • 性能:每次都会重新解析函数体字符串,比普通函数创建方式慢。
  • 安全性:类似 eval,存在代码注入风险。

除非非常特殊的动态代码生成场景(比如模板引擎内部),通常应避免使用。

9.1.5 对象方法简写(Shorthand Method)

ES6 允许在对象字面量中直接定义方法。

const obj = {
  name: 'Alice',
  greet() {
    return `Hello, ${this.name}`;
  }
};
  • 本质上是函数表达式,但比传统写法 greet: function() {...} 更简洁。
  • 不能作为构造函数,没有 prototype
  • this 是动态绑定的。

9.1.6 Generator 函数与 async 函数

这两类特殊函数也是函数的变体:

// Generator
function* gen() {
  yield 1;
}

// async 函数
async function fetchData() {
  const res = await fetch('/api');
  return res.json();
}

它们基于函数声明/表达式的基础形式,但拥有特殊的执行机制(yieldawait),将在第 12 章详细讨论。

9.1.7 关键差异对比

| 特性 | 函数声明 | 函数表达式 | 箭头函数 |
|------------------|-----------------|-------------------|---------------------|
| 提升 | 整体提升 | 变量声明提升,赋值不提升 | 同函数表达式 |
| 语法简洁度 | 一般 | 一般 | 极简 |
| this 绑定 | 动态,取决于调用 | 动态,取决于调用 | 词法继承外层 |
| 可作为构造函数 | 是 | 是 | 否 |
| arguments 对象 | 有 | 有 | 无(用剩余参数替代) |
| prototype 属性 | 有 | 有 | 无 |
| 适合回调中的 this | 需要 bind | 需要 bind | 天然继承外层 |

9.1.8 如何选择

没有一种形式是绝对最优的,关键看使用场景:

  • 通用场景:优先使用函数声明,因为提升带来的灵活性让代码顺序更自由。
  • 需要闭包或条件性创建:使用函数表达式
  • 需要保持外层 this 的回调:用箭头函数
  • 对象方法:用方法简写,像现代代码的风格。
  • 动态构建逻辑极度特殊时:才考虑 new Function(),平时不用。

掌握这些形式,你就能在代码中根据意图优雅地定义行为,而不是被语法卡住。