作为一个前端渣渣,最近在写js代码的时候,开始使用一些ES6的新特性,结果到箭头函数部分“掉坑”了。网上大部分都说箭头函数怎么用,而很少说箭头什么时候 不要用 ,所以这里翻译一篇文章来说说什么时候不要用的问题。
原文地址 ,向原作者表示感谢。
看到代码每天都在进步是一件快乐的事情,在错误中学习,搜索更好的实现,创造新的特性,这些行为让代码在版本迭代中越来越好。
这就是JavaScript这几年发生的事,ES6带来了很多新特性:箭头函数、类等等。这些都是一些伟大的变化,特别是箭头函数。已经有很多的文章对箭头函数的特性进行了介绍,如果你刚刚接触ES6,我建议你去看 这篇文章 。
然而事情都有两面性,新特性某些时候也会带来混乱,其中之一就是乱用箭头函数的问题。
这篇文章举了一些示例来说明什么地方你应该放弃使用箭头函数而去使用旧的函数声明表达式或者短方法语法(shorthand method syntax)。并且给出简短的解决方法,这可以让代码的可读性更高。
JavaScript中的方法就是一个作为对象属性的函数,当一个方法被调用时, this
就指向了方法所属的对象。
因为箭头函数的语法简短,使用它来定义函数方法是很吸引人的。让我们试一下:
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可以参考 廖大的网站
,讲的很详细。)
所以调用 this.arrow
等同于调用 window.arrow
,即 undefined
。
解决方法就是使用函数表达式或者 短语法
(ES6新特性)来定义方法,这种情况下 this
将由调用者决定,而不是闭包中。下面是修改后的版本:
var calculate = { array: [1, 2, 3], sum() { console.log(this === calculate); // => true return this.array.reduce((result, item) => result + item); } }; calculate.sum(); // => 6
同样的规则也适用于 prototype
对象上,使用箭头函数来定义 sayCatName
方法,造成了错误的上下文环境为 window
:
functionMyCat(name){ this.catName = name; } MyCat.prototype.sayCatName = () => { console.log(this === window); // => true return this.catName; }; var cat = new MyCat('Mew'); cat.sayCatName(); // => undefined
使用老的函数表达式来解决:
functionMyCat(name){ this.catName = name; } MyCat.prototype.sayCatName = function(){ console.log(this === cat); // => true return this.catName; }; var cat = new MyCat('Mew'); cat.sayCatName(); // => 'Mew'
当我们使用 cat.sayCatName()
这种方式调用时,我们将上下文环境绑定到了 cat
对象上。
JavaScript中的 this
是一个很给力的特性,它准许在函数调用时改变上下文。调用目标对象时频繁的切换上下文,可以使代码看起来更加“自然”,就好象说“这个对象发生什么事情”一样。
然而箭头函数在声明时候静态的绑定了上下文环境,并且不可动态修改。这样的好处就是不用考虑 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
并且没任何意义。
这时你不得不使用函数表达式,准许 this
根据目标元素来改变。
var button = document.getElementById('myButton'); button.addEventListener('click', function(){ console.log(this === button); // => true this.innerHTML = 'Clicked button'; });
当用户点击按钮后,在回调函数中 this
指向了 button
,因此点击之后 this.innerHTML = 'Clicked button'
正确的修改了按钮文本。
在构造函数中使用 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
作为构造函数来使用。
我认为ES6中明确的给出了详细的错误提示是非常好的,相反在之前的版本中会静默的失败而没有任何提示。
下面的例子使用函数表达式来正确的创建构造函数:
var Message = function(text){ this.text = text; }; var helloMessage = new Message('Hello World!'); console.log(helloMessage.text); // => 'Hello World!'
箭头函数可以省略括号、花括号甚至在函数体只有一句话时候连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
返回两个数的乘积或者使用第一个参数为后续的运算创建一个闭包。
这个函数可以正常使用并且十分简短,但是第一眼看上去太难理解了。
为了可读性,我们将这个箭头函数还原回函数表达式:
functionmultiply(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
在简短和冗余间做出权衡让你的代码更加清晰明确。
毫无疑问箭头函数是一个不错的特性,正确的使用可以让代码更加简单明了,特别是早些时候不得不使用 .bind()
或者捕获上下文的时候。
事物都有两面性,你不能在需要动态上下文环境的情况下使用箭头函数:定义方法时,使用构造函数创建对象时,当处理事件时需要使用 this
从目标获取上下文的情况。