箭头函数和普通函数有什么区别?
简化版
箭头函数语法更短,没有自己的 this、arguments、super 和 new.target,不能作为构造函数,也没有 prototype。它适合回调和函数式写法,不适合对象方法、构造函数和需要动态 this 的场景。
详细版
箭头函数最大的差异是词法 this:
function Timer() {
this.count = 0;
setInterval(() => {
this.count++;
}, 1000);
}
回调里的 this 继承外层 Timer 的 this。
不适合这样写:
const obj = {
name: 'Tom',
say: () => console.log(this.name),
};
这里 this 不指向 obj。另外箭头函数不能 new,也不能用 arguments,需要参数集合时用剩余参数 ...args。
完整版教学
一、箭头函数不是普通函数的简写版
箭头函数确实语法短,但它更重要的设计是“捕获外层 this”。普通函数的 this 看调用方式,箭头函数的 this 看定义位置。
这让它非常适合数组方法和异步回调:
items.map(item => item.id);
二、为什么不能作为构造函数
构造函数需要通过 new 创建实例,并把 this 绑定到新对象。箭头函数没有自己的 this,也没有 prototype,所以不能 new。
const Foo = () => {};
new Foo(); // TypeError
三、arguments 的替代方案
箭头函数没有自己的 arguments,可以用剩余参数:
const sum = (...nums) => nums.reduce((a, b) => a + b, 0);
这种写法更明确,也是真数组,不需要再转换。
四、使用边界
对象方法通常用普通函数,因为方法经常需要 this 指向调用者。类方法也不要随意写成箭头属性,除非明确需要绑定实例 this,并且能接受每个实例创建一份函数的成本。选择时要同时回答两个问题:this 是否需要动态接收者,以及函数是否值得在所有实例间共享。
五、面试追问与工程落地
箭头函数常见追问是“它有没有自己的 arguments”。答案是没有,它访问到的是外层函数的 arguments。现代写法应该用剩余参数 ...args,这不仅更清晰,而且拿到的是真数组。
还会问“箭头函数适不适合写 class 方法”。如果写成类字段箭头函数,它会绑定实例 this,适合传给回调避免 bind;但每个实例都会创建一份函数,不能像原型方法那样共享。组件数量很大或方法很多时,要注意内存成本。
工程里对象方法不要默认写箭头函数,尤其是需要通过调用者决定 this 的 API。比如事件系统、插件钩子、对象字面量方法,都要先判断 this 是否需要动态绑定。
六、用调用结果判断 this 与构造能力
判断箭头函数行为时,不要只看它写在哪里,而要找“定义它时最近一层拥有 this 的非箭头函数或模块环境”。箭头函数不会在调用时执行普通函数的 this 绑定流程,call、apply、bind 传入的新接收者也覆盖不了这个词法 this。同理,它没有自己的 arguments、super 和 new.target,这些名称都沿词法环境向外查找。
function Counter() {
this.value = 1
this.read = () => this.value
}
const counter = new Counter()
console.log(counter.read.call({ value: 99 })) // 1
console.log(Object.hasOwn(counter, 'read')) // true
| 检查项 | 普通函数 | 箭头函数 |
|---|---|---|
call 能否改 this | 能 | 不能 |
是否有 prototype | 通常有 | 没有 |
能否配合 new | 取决于是否有 [[Construct]] | 不能 |
arguments 来源 | 本次调用 | 外层词法环境 |
上例创建 1000 个 Counter 实例时,也会创建 1000 个 read 函数;若方法放在原型上,通常只共享 1 份函数。这不是说实例箭头方法一定错误,而是需要用“稳定绑定”换取相应的实例内存成本。
面试中最好先给出词法绑定规则,再分别说明事件回调、对象方法和类字段三种场景,不能笼统回答“箭头函数的 this 永远指向 window”。
七、常见误区与追问
- 误区:箭头函数的 this 永远指向全局对象。 它捕获外层词法
this;ES 模块顶层的this是undefined,浏览器事件回调中的外层环境也可能不同。 - 误区:用 bind 可以永久修正箭头函数的 this。
bind可以预置参数,但无法替换箭头函数已经捕获的this。 - 误区:箭头函数只是写法更短。 它缺少自己的
this、arguments和构造能力,语义并不等同于普通函数表达式。 - 追问:箭头函数为什么不能 new? 它没有
[[Construct]]内部方法,也没有供实例继承的默认prototype,调用new会抛出TypeError。 - 追问:对象方法适合直接写成箭头函数吗? 若方法需要通过
obj.method()取得obj,通常不适合;箭头函数不会因点调用而绑定接收者。 - 追问:类字段箭头方法有什么代价? 每个实例都会创建并持有一份函数,换来回调传递后仍保持实例
this。 - 追问:没有 arguments 时怎样接收可变参数? 使用剩余参数
(...args),它是真数组,作用域和参数来源也更明确。
八、加强记忆
箭头函数可以记成“短语法 + 词法 this”。它适合回调,不适合构造器;适合继承外层 this,不适合需要调用者决定 this 的方法。
判断口诀可以进一步落成决策:需要动态 this 或构造能力时用普通函数,需要捕获外层 this 的短回调时再考虑箭头函数。