JavaScript 箭头函数用法详解
概述
箭头函数是 ES6 提供的函数简写语法。它不只是省几个字符,最重要的区别是它没有自己的 this,而是沿作用域链向外查找。这一条决定了它适合和不适合的场景。
基本写法:
// 完整形式
const add = (a, b) => {
return a + b;
};
// 单表达式可以省略大括号和 return(隐式返回)
const add2 = (a, b) => a + b;
// 单参数可以省略括号
const double = n => n * 2;
// 无参数必须写空括号
const now = () => Date.now();
返回对象字面量时必须用括号包起来,否则大括号会被解析成函数体:
const makeUser = name => ({ name, id: 1 }); // 正确
// const bad = name => { name, id: 1 }; // 返回 undefined,且 name, id 被当语句
this 的差异
普通函数的 this 在调用时确定,取决于怎么调用;箭头函数的 this 在定义时确定,等于外层作用域的 this,而且无法通过 call、apply、bind 改变。
最典型的场景是回调里的 this 丢失:
const timer = {
seconds: 0,
// 普通函数:setTimeout 回调里的 this 是 undefined(严格模式)或 window
brokenStart() {
setInterval(function () {
this.seconds++; // this 不是 timer
console.log(this.seconds); // NaN
}, 1000);
},
// 箭头函数:this 沿用 brokenStart 的 this,也就是 timer
start() {
setInterval(() => {
this.seconds++;
console.log(this.seconds); // 1, 2, 3...
}, 1000);
},
};
ES6 之前解决这个问题得在外面存一份 const self = this,或者给回调 .bind(this)。箭头函数把这个模式内建到了语法里。
反过来,需要动态 this 的地方绝对不能用箭头函数。DOM 事件处理器就是典型:
const btn = document.querySelector('button');
// 正确:this 指向触发事件的元素
btn.addEventListener('click', function () {
this.classList.toggle('active');
});
// 错误:this 指向定义时的外层作用域(模块顶层是 undefined)
btn.addEventListener('click', () => {
this.classList.toggle('active'); // TypeError
});
对象方法也一样。写成箭头函数,this 会指向定义对象字面量时的外层 this(不是这个对象),因为对象字面量本身不产生作用域:
const obj = {
name: 'wfly',
bad: () => console.log(this.name), // undefined
good() {
console.log(this.name); // 'wfly'
},
};
没有 arguments
箭头函数不绑定 arguments,访问它拿到的是外层函数的 arguments,在顶层则直接 ReferenceError。要收集不定参数用 rest 参数:
const sum = (...nums) => nums.reduce((a, b) => a + b, 0);
console.log(sum(1, 2, 3)); // 6
rest 参数比 arguments 更好用:它是真数组,可以直接 map、filter,而 arguments 是类数组对象,得先 Array.from 转换。
不能当构造函数
箭头函数没有 [[Construct]],也没有 prototype 属性:
const Foo = () => {};
// new Foo(); // TypeError: Foo is not a constructor
console.log(Foo.prototype); // undefined
同理它也不能作为生成器函数,没有 function* 的箭头形式,函数体里不能用 yield。
没有 super 和 new.target
在类的方法里用箭头函数,super 会沿用外层方法的 super 绑定,这通常是我们想要的。但如果需要 new.target 判断是否被 new 调用,箭头函数里拿到的也是外层的值。
关于「更短的写法」的取舍
隐式返回很方便,但滥用会写出不好读的代码。比如链式条件:
// 可读性差
const cls = active => (active ? (disabled ? 'a-d' : 'a') : 'n');
// 好一些
const cls = active => {
if (!active) return 'n';
return disabled ? 'a-d' : 'a';
};
另一个容易犯的错是在 map 里用带大括号的箭头函数但忘了写 return:
const ids = list.map(item => {
item.id; // 忘了 return,结果全是 undefined
});
这类错误 ESLint 的 array-callback-return 规则能查出来,建议开着。
该用哪个的判断标准
用箭头函数:回调函数、需要词法 this 的场景(定时器、Promise.then、数组方法)、纯函数式的短工具函数、class 里需要绑定实例的处理器字段。
用普通函数:对象的方法(用方法简写 method() {})、需要 this 指向调用者的 DOM 事件处理器、构造函数、原型方法、需要 arguments 的兼容老代码、生成器函数。
class 里的处理器字段写成箭头函数还有个隐含代价:它是实例字段,每个实例存一份函数对象,不像原型方法那样共享。列表里渲染成千上万个组件实例时这部分内存不可忽略,权衡的做法是在构造函数里 bind 一次,或者把事件委托到父元素上。

还没有留言,来发表第一条吧~