setTimeout 中的 this 关键字

原文首发于 baishusama.github.io,欢迎围观~

概述

本文主要讲述了几种场景下 setTimeout 的回调函数的 this 绑定出错的几种解决方法。

举个栗子

class Person {
  sayName() {
    console.log("Hello, I'm " + this.name + ".")
  }
  
  constructor(name = "No One") {
    this.name = name
    setTimeout(this.sayName, 1000)
  }
}

var imo = new Person("Imo")

setTimeout(imo.sayName, 2000)

P.S. 上述代码使用了 ES6 语法,这里不做语法上的过多解释。不知所谓的童鞋可以看一下《30 分钟掌握 ES6 核心内容(上)》《30 分钟掌握 ES6 核心内容(下)》这两篇。

上述代码定义了一个 Person 类,这个类有一个构造函数和一个 sayName 方法。其中,sayName 方法内部使用了 this 关键字,用于引用当前实例的(公有成员)变量 name

上述代码中,有两个 setTimeout 函数。第一个 setTimeout 存在于 Person 类的构造函数当中,其第一个参数是 this.sayName;第二个存在于全局作用域中,其第一个参数是 imo.sayName。两者都期望在控制台打印 Hello, I'm Imo.,但是目前两者都没有达到期望——目前打印的均是 Hello, I'm .。出现这种现象的原因是 sayName 方法内部的 this 没有如期地指向 imo 对象,而是错误地指向了全局对象,因而 this.name 的值为 undefined,对应到字符串是 '' (空字符串)。

那么,我们如何修改代码使得 this 关键字如期地指向 imo 对象呢?

第二个 setTimeout

先解决比较简单的第二个 setTimeout

有两种方法:

  1. .bind()
  2. 匿名函数包裹

代码如下:

// 第二个 setTimeout 用“.bind()”方法修改如下:
setTimeout(imo.sayName.bind(imo), 2000)

// 第二个 setTimeout 用“匿名函数包裹”方法修改如下:
setTimeout(function(){
    imo.sayName()
}, 2000)

.bind()”方法

Apply 调用模式 - The Apply Invocation Pattern

Function.prototype.bind() @MDN:“bind() 方法会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this ...”

“匿名函数包裹”方法

方法调用模式 - The Method Invocation Pattern

在外层匿名函数的内部,sayName 作为 imo 对象的方法被调用,方法内部的 this 也就被绑定到 imo 对象了。

第一个 setTimeout

我们尝试上面提到的两种解决方法。

.bind()”方法

.bind() 方法依旧可行,代码:

// 第一个 setTimeout 用“.bind()”方法修改如下:
setTimeout(this.sayName.bind(this), 1000)

“匿名函数包裹”方法

“匿名函数包裹”方法好像让事情更复杂了——修改后但没能解决问题的代码如下:

// 第二个 setTimeout 用“匿名函数包裹”方法修改如下:
setTimeout(function(){
    this.sayName()
}, 1000)

此时,this.sayName 的值为 undefinedsayName 方法甚至都没有机会得到调用。

“匿名函数包裹+.bind

在包裹匿名函数后的代码的基础上,仍可以使用 .bind 方法。

// 第二个 setTimeout 用“匿名函数包裹+.bind”方法修改如下:
setTimeout(function(){
    this.sayName()
}.bind(this), 1000)

“匿名函数包裹+that

可以利用匿名函数形成闭包,引用正确的 this

// 第二个 setTimeout 用“匿名函数包裹+that”方法修改如下:
var that = this
setTimeout(function(){
    that.sayName()
}, 1000)

“箭头函数”方法

ES6 的箭头函数没有自己的 this 、直接继承外部作用域的 this ,所以还可以这么改:

// 第二个 setTimeout 用“箭头函数”方法修改如下:
setTimeout(()=>{this.sayName()}, 1000)

小结

setTimeout 的回调函数中出现 this 的时候,要特别注意其绑定的对象是否和预想的一致。当绑定有误时可以通过下述方法解决。

setTimeout 的回调函数是一个能够通过变量引用的对象的方法(类似于例子中的 imo.sayName)时,有两种解决方法:

  1. .bind()”方法
  2. “匿名函数包裹”方法

setTimeout 的回调函数是一个通过 this 访问的方法(类似于例子中的 this.sayName)时,有两种解决方法:

  1. .bind()”方法
  2. “ES6箭头函数”方法

setTimeout 的回调函数是一个含有 this 的匿名函数(类似于例子中的 this.sayName 被匿名函数包裹后)时,有两种解决方法:

  1. .bind()”方法
  2. “闭包that”方法

可以看出,解决方法中的“.bind()”方法是万金油。所以,如果你记不住这么多方法,至少也要记住“.bind()”方法。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 一、ES6简介 ​ 历时将近6年的时间来制定的新 ECMAScript 标准 ECMAScript 6(亦称 ...
    一岁一枯荣_阅读 6,126评论 8 25
  • 1. this之谜 在JavaScript中,this是当前执行函数的上下文。因为JavaScript有4种不同的...
    百里少龙阅读 1,033评论 0 3
  • 函数参数的默认值 基本用法 在ES6之前,不能直接为函数的参数指定默认值,只能采用变通的方法。 上面代码检查函数l...
    呼呼哥阅读 3,482评论 0 1
  • 箭头函数与传统JavaScript的不同 先来谈谈ES5中的this 在ES5中,每个函数在被调用时都会自动取得t...
    打铁大师阅读 1,307评论 4 21
  • 听了很多的心灵鸡汤也看过很多反鸡汤的文章节目,所有的这一切都是希望能让自己的生活按照自己的想法去过。但往往自己的想...
    飞旅爷阅读 331评论 0 0