箭头函数和普通函数有哪些区别?
箭头函数没有自己的 this、arguments 和 super,会沿用外层的;它不能用 new 调用,也没有 prototype 属性;函数体里不能用 yield。所以它适合写回调,不适合做对象方法、构造函数和需要动态 this 的场景。
简答
写回调时,
[1, 2, 3].forEach(() => this.count++)里的this还是外面方法的this,换成普通函数就丢了。这是箭头函数最常被用到的区别。MDN 的说法是,箭头函数没有自己的
this、arguments和super绑定,用到时会去外层找。所以call、apply、bind也改不了它的this。它还不能当构造函数:用
new调用会抛TypeError,它也没有prototype属性。函数体里不能用yield,所以也写不成 Generator。因此箭头函数适合写回调和短小的工具函数,不适合写对象方法、原型方法、构造函数,以及事件监听里需要
this指向当前元素的场景。
先看几个区别
function Normal() {}
const Arrow = () => {};
console.log(typeof Normal.prototype); // object
console.log(Arrow.prototype); // undefined
try {
new Arrow();
} catch (error) {
console.log(error.name); // TypeError
}
function outer() {
const arrow = () => arguments[0];
return arrow('箭头自己的参数');
}
console.log(outer('外层参数')); // 外层参数
const timer = {
seconds: 0,
start() {
[1, 2, 3].forEach(() => {
this.seconds += 1;
});
return this.seconds;
},
};
console.log(timer.start()); // 3
前两行说明箭头函数没有 prototype。new 一个函数时要拿它的 prototype 做新对象的原型,箭头函数既没有这个属性,也不允许被 new,所以直接抛 TypeError。
为什么 arrow('箭头自己的参数') 返回的是外层参数?因为箭头函数没有自己的 arguments,它里面的 arguments 指的是外层 outer 的。想拿箭头函数自己的参数,用剩余参数 (...args) => args[0]。
最后的 timer.start() 得到 3,因为 forEach 的回调是箭头函数,this 沿用 start 方法的 this,也就是 timer。换成普通函数,回调的 this 在非严格模式下是全局对象,严格模式下是 undefined,计数就加不到 timer 上了。
什么时候不要用箭头函数
正因为 this 来自外层,把箭头函数直接写成对象字面量的方法时,this 不指向这个对象,而是对象外面那层的 this。同理,addEventListener 的回调如果要用 this 拿到当前元素,也要用普通函数,或者改用 event.currentTarget。这些场景的具体分析见《普通函数和箭头函数的 this 有什么区别?》。
类字段里写 handle = () => {} 是另一回事。它能保证 this 永远是实例,常用来传回调。但 MDN 提醒,类字段定义在实例上而不是原型上,每创建一个实例就会多一个新函数。
写法上的区别
箭头函数只有一个简单参数(没有默认值、解构或剩余参数)时可以省略括号;函数体只有一个表达式时可以省略花括号,表达式的值就是返回值。要直接返回对象字面量,必须用括号包起来,写成 () => ({ id: 1 }),否则花括号会被当成函数体。
常见追问
箭头函数能用 call 或 bind 改 this 吗?
不能。传给
call、apply、bind的this参数会被忽略,参数照常传入。它的this只取决于定义时外层的this。
async 箭头函数可以吗?
可以,
async () => {}是合法的,里面能用await,this同样沿用外层。只是不存在“箭头 Generator”,因为箭头函数体里不能用yield。