箭头函数如何处理this
关键字
下面是代码示例,点击button
会触发从5
到-1
的倒计时
<button class="start-btn">Start Counter</button>
...
const startBtn = document.querySelector(".start-btn");
startBtn.addEventListener('click', function() {
this.classList.add('counting')
let counter = 5;
const timer = setInterval(() => {
this.textContent = counter
counter --
if(counter < 0) {
this.textContent = 'THE END!'
this.classList.remove('counting')
clearInterval(timer)
}
}, 1000)
})
addEventListener
的回调函数时常见的匿名函数
,在这里,我并没有使用箭头函数,为什么呢?如果你在匿名函数
内打印this
,你会发现,它指向的是button
元素(当给button
元素绑定事件函数时,内部this
指向的是元素本身),这恰恰是我们希望的结果,如果将上述的匿名函数
换成箭头函数,会怎样呢?
startBtn.addEventListener('click', () => {
console.log(this)
...
})
此时this
指向Window
全局对象
这意味着,如果你想在箭头函数内为button
元素添加class
类,则必须先用querySelector
或其他获取到该元素
匿名箭头函数
在上面的例子中,我们注意到,setInterval
里面是匿名箭头函数
,这是为何呢?
如果我们将其换成普通函数,this
指向什么呢?
const timer = setInterval(function() {
console.log(this)
...
}, 1000)
它有可能是button
元素吗?当然不会,此时它指向的是Window
对象
事实上,当this
处于unbound
或global
函数内,而该函数作为参数传递给.setInterval()
,此时上下文已发生改变,导致了this
指向了全局作用域
unbound
函数,指的是该函数没有绑定在对象上,所以该函数内的this
指向全局或window
对象
在ES6
之前,我们常用的解决办法是,在外部存储this
值,从而保证它一直指向button
元素
const that = this
const timer = setInterval(function() {
console.log(that)
...
}, 1000)
你也可以使用.bind()
解决该问题
const timer = setInterval(function() {
console.log(this)
...
}.bind(this), 1000)
有了箭头函数后,这个问题便不存在了
箭头函数并没有它们自己的this
上下文,它们从父级继承this
值。在.setInterval()
外面,this
指向的是button
元素,所以在.setInterval()
的箭头函数内,this
也指向button
元素。
箭头函数
并非总是工作上的良友
在JavaScript内,箭头函数并非只是新的
、有趣的
函数编写方式,它们有自己的局限性。这意味着某些场景下,你不能使用它们。在上例中,click
处理函数便是这样的情况,而它并不是唯一的特例,现在让我们列举更多不适用箭头函数的例子吧
使用箭头函数作为对象的方法
const netflixSeries = {
title: 'After Life',
firstRealease: 2019,
likes: 5,
getLikes: () => `${this.title} has ${this.likes} likes`,
addLike: () => {
this.likes++
return `Thank you for liking ${this.title}, which now has ${this.likes} likes`
}
}
在netflixSeries
对象内,我们调用console.log(netflixSeries.getLikes())
,期望打印出当前title
包含的likes
值信息,可事实上,却返回的是undefined has NaN likes
调用console.log(netflixSeries.addLike())
,期望的是likes
值加1,在控制台里打包含该值的thank you
信息,可现实又一次伤害了我们,打印的却是Thank you for liking undefined, which now has NaN likes
很明显,this.title
和this.likes
并没有指向对象里的title
和likes
属性
这个问题,正是因为箭头函数的词法作用域(lexical scoping
),还记得我们上面说的吗?箭头函数并没有自己的this
作用域,它永远继承自父类的this
,在此例中,箭头函数的父类作用域是Window
对象,并非netflixSeries
对象
解决上述问题的方法便是,使用普通函数
const netflixSeries = {
title: 'After Life',
firstRealease: 2019,
likes: 5,
getLikes() {
return `${this.title} has ${this.likes} likes`
},
addLike() {
this.likes++
return `Thank you for liking ${this.title}, which now has ${this.likes} likes`
}
}
// 调用方法
console.log(netflixSeries.getLikes())
console.log(netflixSeries.addLike())
// 输出:
After Life has 5 likes
Thank you for liking After Life, which now has 6 likes
第三方库内使用箭头函数
在第三方库内,通常会绑定方法调用,使其里面的this
指向有用的事物
比如,在 jQuery
事件处理器内,this
指向该方法绑定的DOM
元素
$('body').on('click', function() {
console.log(this)
})
// <body>
如果我们使用箭头函数
,那么this
指向的便是Window
对象
$('body').on('click', () =>{
console.log(this)
})
// Window
大出意料之外,是吗?
Vue
类似的例子代码如下所示:
new Vue({
el: app,
data: {
message: 'Hello, World!'
},
created: function() {
console.log(this.message);
}
})
// Hello, World!
在created
钩子函数内,this
被绑定到Vue
的实例对象上,所以this.message
打印的是Hello, World!
如果我们使用箭头函数,this
指向的便是父级作用域,而该作用域肯定没有message
属性
new Vue({
el: app,
data: {
message: 'Hello, World!'
},
created: function() {
console.log(this.message);
}
})
// undefined
箭头函数没有arguments
对象
在某些情况下,函数可能接收无数个参数,以前,我们通常在函数内使用arguments
,代指所有的参数,可是若在箭头函数内,使用arguments
,便会抛出错误Uncaught ReferenceError: arguments is not defined
const listYourFavNetflixSeries = () => {
// we need to turn the arguments into a real array
// so we can use .map()
const favSeries = Array.from(arguments)
return favSeries.map( (series, i) => {
return `${series} is my #${i +1} favorite Netflix series`
} )
console.log(arguments)
}
console.log(listYourFavNetflixSeries('Bridgerton', 'Ozark', 'After Life'))
这意味着,在箭头函数内,没有arguments
对象,如果你需要使用arguments
对象,则需使用普通函数
如果你执意要使用箭头函数(人家就是这么任性啦~~~~),那你可以使用ES6
的扩展符(...
),如下面代码所示:
const listYourFavNetflixSeries = (...seriesList) => {
return seriesList.map( (series, i) => {
return `${series} is my #${i +1} favorite Netflix series`
} )
}
结束语
箭头函数
让我们无需使用远古时代里的hack
方法(比如bind(this)
),解决this
的指向问题。它通常与一些数组方法配合使用,效果颇佳,比如.map()
、.sort()
、.forEach()
、filter()
以及reduce
。但是请记住,箭头函数
无法替代普通函数,我们需要在合适的场景里使用它。