箭头函数的优点和缺点
ES6标准新增了一种新的函数:Arrow Function(箭头函数)
箭头函数表达式的语法比函数表达式更简洁,并且没有自己的this,arguments,super或 new.target。这些函数表达式更适用于那些本来需要匿名函数的地方,并且它们不能用作构造函数。
举个例子,foo和bar在做同样的事情。
| let foo = function() { console.log('BAZ') } let bar = () => { console.log('BAZ') } foo() // BAZ bar() // BAZ |
|---|
但是箭头函数不仅仅是经典函数表达式的语法糖。我们可以发现两者之间有很大的区别。这些区别使箭头功能变得非常好用,但如果不小心,这可能就变成大坑。有时箭头函数不太管用,这时候依然需要使用经典的函数表达式。
箭头函数和this
在经典函数表达式中,根据调用函数的上下文,this关键字绑定为不同的值。而箭头函数在其词法作用域内使用this。这导致了非常不同的行为。
上下文和作用域有什么区别?上下文(大致)是调用函数的对象。作用域是定义函数的所有变量。一个关心它是如何被调用的,另一个关心它是如何被定义的。
作为上下文的一个例子,定义一个对象,这个对象里有一个函数表达式方法。
| let obj = { myVar: 'foo', myFunc: function() { console.log(this.myVar) } } obj.myFunc() // foo |
|---|
obj对象能调用myFunc函数,这就是myFunc的上下文。所以此时在myFuns中this的值被绑定为obj。根据函数的调用方式,可以用不同的方式定义上下文。例如,当用new关键字调用构造函数时,它的this被绑定到正在构造的新对象。context on MDN
因此,如果它绑定到上下文(即绑定到调用函数的对象),它可能会导致回调出现一些非常棘手的问题。让我们在我们的对象my FuC中添加一个setTimeout来模拟回调:
| let obj = { myVar: 'foo', myFunc: function() { console.log(this.myVar) setTimeout(function() { console.log(this.myVar) }, 1000) } } obj.myFunc() // foo ... then... undefined |
|---|
myFunc的this值取决于obj,因此,myFunc.myVar能够成功从foo打印出来。然而,第二个函数被setTimeout 调用,因此它的上下文不同。它的上下文实际上是节点中的超时对象或浏览器中的窗口对象,所以尽管我们可能依然想让他指向obj,但是已经失去了绑定。
此时需要一点小技巧。一种方法就是赋值给一个变量,通常我们命名为self或者that。这个变量在回调函数的词法范围内。这意味着回调函数可以访问该变量,因为它是在其范围内定义的:
| let obj = { myVar: 'foo', myFunc: function() { let self = this console.log(this.myVar) setTimeout(function() { console.log(self.myVar) }, 1000) } } obj.myFunc() // foo ... then... foo |
|---|
也可以使用bind、call和apply等方法来实现这一点。
使用箭头函数有一个更简单的解决方案。回想一下,我们说过箭头函数从词法范围中获取它们的值。这意味着它只是在周围的代码块中使用这个值。它不在乎叫它什么,它只在乎它在哪里被定义
| let obj = { myVar: 'foo', myFunc: function() { console.log(this.myVar) setTimeout(() => { console.log(this.myVar) }, 1000) } } obj.myFunc() // foo ... then... foo |
|---|
因此,我们可以立即看到箭头函数更适合回调。但是如果我们尝试使用箭头函数作为对象方法会发生什么呢?
| let obj = { myVar: 'foo', myFunc: () => { console.log(this.myVar) } } obj.myFunc() // undefined |
|---|
你可能希望this指向obj。但是箭头函数不会将它绑定到调用它们的对象。他们只是在定义的范围内使用这个值。在这种情况下,this指向全局对象。所以箭头函数不能用于对象方法!
要点:函数表达式适合对象方法。箭头函数适合回调或者map、reduce、forEach等方法。
您可以在MDN上阅读更多关于作用域的内容。从根本上讲,箭头函数根本无法将this的值与其作用域内的this值绑定。所以使用bind, call, 和 apply方法,没有任何影响。
构造方法
箭头函数同样不适用于构造方法。经典的函数表达式可以用来构造这样一个新对象:
| let Person = function(name, height) { this.name = name this.height = height } Person.prototype.hello = function() { console.log('Hi, my name is ' + this.name) } let alice = new Person('Alice', 1.7) alice.hello() // Hi, my name is Alice |
|---|
但是箭头函数没有prototype 原型。不能和new
绑定参数
我们已经看到箭头函数如何不绑定this,它们只是在作用域中使用this。箭头函数也不绑定arguments对象。通过函数表达式,您可以做到这一点:
| let sum = function() { let args = Array.from(arguments) // arguments is available return args.reduce((a, b) => a + b, 0) } sum(1, 2, 3) // 6 |
|---|
箭头函数没有arguments对象,但是使用rest参数也可以实现相同的功能:。
| let sum4 = (...args) => { return args.reduce((a, b) => a + b, 0) } sum(1, 2, 3) // 6 |
|---|
隐式返回值
我们可以用箭头函数使上面的函数更加简洁。使用简洁的形式,我们不必写一个代码块,只需定义一个表达式,箭头函数就会自动返回它的值:
| let sum = (...args) => args.reduce((a, b) => a + b, 0) |
|---|
除此之外,我们可以通过用括号括起来返回对象文字:
| let getObj = () => ({ foo: 'hello', bar: 'world' }) |
|---|
这种简洁的语法使得箭头函数在定义小而易读的回调时更好。
总结
所以箭头函数与传统的函数表达式有很大不同。它们的一些特性,在作为回调函数时非常好用。但是它们很难用做对象方法和构造函数。还有一些其他的区别,例如,箭头函数不能是生成器。可以看去MDN上看到更多细节。