前端知库

箭头函数和普通函数有哪些区别?

箭头函数没有自己的 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 指向当前元素的场景。

先看几个区别

JavaScript
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。