你真的了解JavaScript里的箭头函数(Arrow Function)吗(二)?

箭头函数如何处理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处于unboundglobal函数内,而该函数作为参数传递给.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.titlethis.likes并没有指向对象里的titlelikes属性

这个问题,正是因为箭头函数的词法作用域(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。但是请记住,箭头函数无法替代普通函数,我们需要在合适的场景里使用它。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值