深入了解 ES6 箭头函数
ES6 最漂亮的功能之一,如果举办选美比赛,它可以轻松赢得选美比赛。很多人不知道的是,箭头函数不仅仅是一种语法糖,我们可以使用它来代替常规回调。
正如我喜欢向参加我的培训/研讨会的人解释的那样,箭头函数是this-less、arguments-less、new.target-less 和super-less。
现在让我们跳过较短的语法,深入了解箭头函数的细节。
词法绑定 this
以前,如果将常规函数用作回调,则将其 this 值设置为全局对象,以防使用 new 运算符调用新对象,或者对于像 jQuery 这样的库,它们将被设置为在事件处理程序的情况下触发事件的对象,或$.each 迭代中的当前元素。即使对于有经验的开发人员来说,这种情况也非常令人困惑。
假设您有一段如下所示的代码。
var obj = {
nameValue: 'default',
initializeHandlers: function() {
var nameInput = document.querySelector('#name');
nameInput.addEventListener('blur', function(event) {
this.nameValue = event.target.value;
});
}
};
obj.initializeHandlers();
问题是blur 事件处理程序中的this 被设置为全局对象而不是obj。在严格模式下——‘use strict’;——你可能会破坏你的应用程序,因为this 被设置为undefined。为了回避这个问题,我们有两个选择:
两个选项如下图所示。
[...]
initializeHandlers: function() {
var nameInput = document.querySelector('#name');
// more elegant but we can do better
var blurHandler = function(event) {
this.nameValue = event.target.value;
}.bind(this)
nameInput.addEventListener('blur', blurHandler);
}
[...]
[...]
initializeHandlers: function() {
var nameInput = document.querySelector('#name');
// ugly and error-prone
var self = this;
nameInput.addEventListener('blur', function(event) {
self.nameValue = event.target.value;
});
}
[...]
另一方面,箭头函数没有内部上下文。它们从外部范围继承上下文。让我们看看箭头函数是如何解决这个问题的。
const obj = {
nameValue: 'default',
initializeHandlers: function() {
const nameInput = document.querySelector('#name');
nameInput.addEventListener('blur', (event) => {
// this references obj instead of the global object
this.nameValue = event.target.value;
});
}
};
在我们的新实现中,this 是对 obj 对象的硬引用,不会因为嵌套而丢失。
词法参数
您是否曾尝试在箭头函数中访问arguments 对象?我有,我浪费了 3 个小时来试图弄清楚为什么我得到外部函数的参数而不是箭头函数的参数。
值得庆幸的是,MDN 存在,并且按照良好的实践要求,您在最后检查文档,当您坐在角落里,膝盖放在胸前,摇晃着对自己重复:“我应该成为一名木匠!”
除了有趣之外,箭头函数不会暴露 arguments 对象。如果您尝试访问它,您将获得周围函数的参数。在我们的例子中,考虑到外部函数也是一个箭头函数,并且我们没有更多的函数,我们将得到一个ReferenceError。
const variadicAdder = (x) => {
return () => {
let args = Array.prototype.slice.call(arguments, 0);
return args.reduce((accumulator, current) => {
return accumulator + current;
}, x);
}
}
const variadicAdderOf5 = variadicAdder(5);
console.log(variadicAdderOf5(10, 11, 12));
// ReferenceError: arguments is not defined
这里没有修复,因为没有任何损坏。我们可以做的是从我们的variadicAdder() 返回一个普通函数,而不是一个箭头。
这将使我们有机会毫无问题地访问arguments 对象。更新后的代码将如下所示,唯一不同的是
它实际上会起作用并且不会引发错误。
const variadicAdder = (x) => {
return function() {
let args = Array.prototype.slice.call(arguments, 0);
return args.reduce((accumulator, current) => {
return accumulator + current;
}, x);
}
}
const variadicAdderOf5 = variadicAdder(5);
console.log(variadicAdderOf5(10, 11, 12));
// 38
要了解有关Array.prototype.reduce 的更多信息,请访问Mozilla Developer Network。
其他特点
正如我在本文的介绍部分中提到的,箭头函数除了上下文和参数之外还有其他几个特征。
我想提的第一件事是您无法将new 运算符与箭头函数一起使用。直接暗示,箭头函数也没有super()。像下面这样的片段只会抛出TypeError。
const Person = (name) => {
this.name = name;
};
let p = new Person('John');
// TypeError: Person is not a constructor
第三个特征,也就是无法使用new 运算符的直接含义,是箭头函数没有new.target 的事实。简而言之,new.target 允许您检测函数是否已作为构造函数调用。
箭头函数,从其周围范围继承 new.target。如果外部作用域是一个函数,并且像构造函数一样调用它(例如new Person('Adrian');),那么new.target 将指向外部函数。
Mozilla 开发者网络在 new.target 上提供了详细说明,我鼓励您查看。
这篇文章也发表在我的博客上,这里:/es6-arrow-functions-in-depth/