转载至 http://www.zcfy.cc/article/when-not-to-use-arrow-functions-482.html
看到你使用的编程语言每天都在不断地进化,是一件非常高兴的事情。从错误中学习,发现更好的实现方法,创造新的特性,让这个过程持续迭代。
JavaScript 这些年正在发生变化,ECMAScript 6 提升了这门语言的可用性:箭头函数,类以及更多内容。这些都很棒!
最有价值的新特性就是箭头函数。现在有许多好文章都在描述它的上下文透明性和短语法。如果你新接触 ES6 ,先读读这篇文章。
但是凡事都有两面。通常新的特性会介绍的有些混乱,其中之一就是箭头函数被误用。
这篇文章通过情景引导,让你知晓哪些情况下应该绕过箭头函数,使用更赞成的旧的函数表达式 或者是新的短方法语法。并且注意一下短语法,因为它可能会影响代码可读性。
1. 在对象上定义方法
在 JavaScript 中,方法作为一个函数存储为对象的一个属性。当调用方法时,this
指向该方法的从属对象。
1a. 对象字面量
自从箭头函数有了短语法,非常吸引人用它来定义方法。让我们试试看:
var calculate = { array: [1, 2, 3], sum: () => { console.log(this === window); // => true return this.array.reduce((result, item) => result + item); } }; console.log(this === window); // => true // Throws "TypeError: Cannot read property 'reduce' of undefined" calculate.sum();
calculate.sum
方法是通过箭头函数定义的。但是在调用 calculate.sum()
的时候抛出了一个 TypeError
,因为 this.array
的值是 undefined
。
当调用 calculate
对象上的方法 sum()
时,上下文仍然是 window
。这个情况发生是因为箭头函数绑定了该window
对象的词法上下文。
执行 this.array
等同于 window.array
, 值为 undefined
。
解决方案是使用函数表达式或者方法定义的短语法 (ECMAScript 6可用)。 在这种情况下 this
决定于调用的对象,而不是紧邻上下文。 让我们看看修正的版本:
var calculate = { array: [1, 2, 3], sum() { console.log(this === calculate); // => true return this.array.reduce((result, item) => result + item); } }; calculate.sum(); // => 6
因为 sum
是一个普通函数,调用 calculate.sum()
的 this
是 calculate
对象。 this.array
是数组的引用,因此元素之和计算正确,结果是: 6
.
1b. 对象原型
相同的规则也适用于在 prototype
对象上定义方法。
用箭头函数来定义 sayCatName
方法,会带来一个不正确的上下文 window
:
function MyCat(name) { this.catName = name; } MyCat.prototype.sayCatName = () => { console.log(this === window); // => true return this.catName; }; var cat = new MyCat('Mew'); cat.sayCatName(); // => undefined
使用 保守派 函数表达式:
function MyCat(name) { this.catName = name; } MyCat.prototype.sayCatName = function() { console.log(this === cat); // => true return this.catName; }; var cat = new MyCat('Mew'); cat.sayCatName(); // => 'Mew'
sayCatName
普通函数被当做方法调用的时候 cat.sayCatName()
,会把上下文改变为 cat
对象。
2. 结合动态上下文的回调函数
this
在 JavaScript 中是一个很强大的特性。它允许利用函数调用的方式改变上下文。通常来说,上下文是一个调用发生时候的目标对象,让代码更加 自然化。这就好像 “某些事情正发生在该对象上”。
无论如何,箭头函数在声明的时候都会绑定静态的上下文,而不会是动态的。这是词素 this
不是很必要的一种情况。
给 DOM 元素装配事件监听器是客户端编程的一个通常的任务。一个事件用 this
作为目标元素去触发处理函数。这是一个动态上下文的简便用法。
接下来的例子试图使用一个箭头函数触发一个处理函数:
var button = document.getElementById('myButton'); button.addEventListener('click', () => { console.log(this === window); // => true this.innerHTML = 'Clicked button'; });
this
在箭头函数中是 window
,也就是被定义为全局上下文(译者注:这里应该描述的就是上文的例子)。当一个点击事件发生的时候,浏览器试着用 button
上下文去调用处理函数,但是箭头函数病不会改变它已经预定义的上下文。
this.innerHTML
等价于 window.innerHTML
,并没有什么意义。
你不得不应用一个函数表达式,去允许目标元素改变其上下文。
var button = document.getElementById('myButton'); button.addEventListener('click', function() { console.log(this === button); // => true this.innerHTML = 'Clicked button'; });
当用户点击该按钮,this
在处理函数中是 button
。 从而 this.innerHTML = 'Clicked button'
正确地修改了按钮的文本去反映点击状态。
3. 调用构造器
this
在一个构造调用过程中是一个新创建的对象。 当执行 new MyFunction()
,该构造器的上下文 MyFunction
是一个新的对象: this instanceof MyFunction === true
.
注意一个箭头函数不能作为构造器。 JavaScript 会通过抛出异常的方式进行隐式地预防。
无论怎样,this
还是会从紧邻上下文中获取,而不是那个新创建的对象。 换句话说,一个箭头函数构造器的调用过程没有什么意义,反而会产生歧义。
让我们看看,如果试图去尝试使用箭头函数作为构造器,会发生什么:
var Message = (text) => { this.text = text; }; // Throws "TypeError: Message is not a constructor" var helloMessage = new Message('Hello World!');
执行 new Message('Hello World!')
, Message
是一个箭头函数, JavaScript 抛出了一个 TypeError
,这意味着 Message
不能被用作于构造器。
与一些之前特定版本的 JavaScript 静默失败 相比,我认为 ECMAScript 6 在这些情况下提供含有错误信息的失败会更加高效。
上面的例子可以使用一个 函数表达式 来修正,这才是创建构造器正确的方式 (包括 函数声明):
var Message = function(text) { this.text = text; }; var helloMessage = new Message('Hello World!'); console.log(helloMessage.text); // => 'Hello World!'
4. 最短语法
箭头函数有一个非常棒的属性,如果函数体只有一条语句的话,可以省略参数的括号 ()
,代码块的花括号 {}
以及 return
(译者注:此处省略参数的括号,与函数体只有一条语句没关系。)。这对写特别短的函数很有帮助。
我大学的编程教授布置给学生一个有趣的任务:用C语言写一个最短的函数去统计字符串长度。这是一个学习和探索新语言非常好的途径。
然而在真实世界中应用程序的代码是会被许多其他的开发者进行阅读的。最短语法不太适合去帮助你的同事快速理解函数的意义。
在某些程度上来说,压缩的函数会变得阅读困难,所以最好别走入愤怒的深渊。让我们来看一个例子:
let multiply = (a, b) => b === undefined ? b => a * b : a * b; let double = multiply(2); double(3); // => 6 multiply(2, 3); // => 6
multiply
返回了两个数字的乘积结果,或者说是一个为了接下来的乘法运算,而关联了第一个参数的闭包。
这个函数运行的很好并且看上去很短。但是它可能第一眼看上去有点难以理解。
为了让它更具有可读性,可以通过给箭头函数添加一些可选的花括号,以及 return
语句,或者是干脆用一个普通函数:
function multiply(a, b) { if (b === undefined) { return function(b) { return a * b; } } return a * b; } let double = multiply(2); double(3); // => 6 multiply(2, 3); // => 6
最好可以在短和冗长之间寻找到一个平衡点,让你的 JavaScript 更加直接。
5. 结论
毫无疑问,箭头函数是一个非常好的特性增强。使用正确的话,它会在很多地方带来方便,比如早期的时候,你不得不使用 .bind()
或者 试图去捕获上下文。当然,它也让代码变得更加轻便。
在某些情况下,优势也会带来劣势。当要求动态上下文的时候,你就不能使用箭头函数,比如:定义方法,用构造器创建对象,处理时间时用 this
获取目标。