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继承自外层词法作用域,无法被call、apply、bind改变(详见 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();
}
它们基于函数声明/表达式的基础形式,但拥有特殊的执行机制(yield 或 await),将在第 12 章详细讨论。
9.1.7 关键差异对比
| 特性 | 函数声明 | 函数表达式 | 箭头函数 |
|------------------|-----------------|-------------------|---------------------|
| 提升 | 整体提升 | 变量声明提升,赋值不提升 | 同函数表达式 |
| 语法简洁度 | 一般 | 一般 | 极简 |
| this 绑定 | 动态,取决于调用 | 动态,取决于调用 | 词法继承外层 |
| 可作为构造函数 | 是 | 是 | 否 |
| arguments 对象 | 有 | 有 | 无(用剩余参数替代) |
| prototype 属性 | 有 | 有 | 无 |
| 适合回调中的 this | 需要 bind | 需要 bind | 天然继承外层 |
9.1.8 如何选择
没有一种形式是绝对最优的,关键看使用场景:
- 通用场景:优先使用函数声明,因为提升带来的灵活性让代码顺序更自由。
- 需要闭包或条件性创建:使用函数表达式。
- 需要保持外层
this的回调:用箭头函数。 - 对象方法:用方法简写,像现代代码的风格。
- 动态构建逻辑极度特殊时:才考虑
new Function(),平时不用。
掌握这些形式,你就能在代码中根据意图优雅地定义行为,而不是被语法卡住。