带你搞懂this

2021/4/29 JavaScript

源链接:https://juejin.cn/post/6946021671656488991 (opens new window)

译文链接:https://juejin.cn/post/6939144174616707103 (opens new window)

记住一个七步口诀:

箭头函数、new、bind、apply 和 call、欧比届点(obj.)、直接调用、不在函数里。

按照口诀的顺序,只要满足前面某个场景,就可以确定 this 指向了。

# 什么是 this

在传统面向对象的语言中,比如Java,this关键字用来表示当前对象本身,或当前对象的一个实例,通过this关键字可以获得当前对象的属性和调用方法。

在JavaScript中,this似乎表现地略有不同,这也是让人“讨厌”的地方~

ECMAScript 规范中这样写:

this 关键字执行为当前执行环境的 ThisBinding。

MDN 上这样写:

In most cases, the value of this is determined by how a function is called. 在绝大多数情况下,函数的调用方式决定了this的值。

可以这样理解,在 JavaScript 中,this的指向是调用时决定的,而不是创建时决定的,这就会导致this的指向会让人迷惑,简单来说,this具有运行期绑定的特性。this的指向得看函数调用时。

# 箭头函数

箭头函数排在第一个是因为它的 this 不会被改变,所以只要当前函数是箭头函数,那么就不用再看其他规则了。

箭头函数的 this 是在创建它时外层 this 的指向。这里的重点有两个:

  1. 创建箭头函数时,就已经确定了它的 this 指向。
  2. 箭头函数内的 this 指向外层的 this

所以要知道箭头函数的 this 就得先知道外层 this 的指向,需要继续在外层应用七步口诀。

# new

当使用 new 关键字调用函数时,函数中的 this 一定是 JS 创建的新对象。

可能会有疑问,“如果使用 new 关键调用箭头函数,是不是箭头函数的 this 就会被修改呢?”。

func = () => {} 
new func() // VM132:2 Uncaught TypeError: func is not a constructor at <anonymous>:2:1
1
2

箭头函数不能当做构造函数,所以不能与 new 一起执行。

# bind

bind 是指 Function.prototype.bind() (opens new window)

# 多次 bind 时只认第一次 bind 的值

function func() {
  console.log(this)
}

func.bind(1).bind(2)() // 1
1
2
3
4
5

# 箭头函数中 this 不会被修改

func = () => {
  // 这里 this 指向取决于外层 this,参考口诀 7 「不在函数里」
  console.log(this)
}

func.bind(1)() // Window,口诀 1 优先
1
2
3
4
5
6

# bind 与 new

function func() {
  console.log(this, this.__proto__ === func.prototype)
}

boundFunc = func.bind(1)
new boundFunc() // Object true,口诀 2 优先
1
2
3
4
5
6

# apply 和 call

apply()call() 的第一个参数都是 this,区别在于通过 apply 调用时实参是放到数组中的,而通过 call 调用时实参是逗号分隔的。

# 箭头函数中 this 不会被修改

func = () => {
  // 这里 this 指向取决于外层 this,参考口诀 7 「不在函数里」
  console.log(this)
}

func.apply(1) // Window,口诀 1 优先
1
2
3
4
5
6

# bind 函数中 this 不会被修改

function func() {
  console.log(this)
}

boundFunc = func.bind(1)
boundFunc.apply(2) // 1,口诀 3 优先
1
2
3
4
5
6

# 欧比届点(obj.)

function func() {
  console.log(this.x)
}

obj = { x: 1 }
obj.func = func
obj.func() // 1
1
2
3
4
5
6
7

# 直接调用

在函数不满足前面的场景,被直接调用时,this 将指向全局对象。在浏览器环境中全局对象是 Window,在 Node.js 环境中是 Global。

先来个简单的例子:

function func() {
  console.log(this)
}

func() // Window
1
2
3
4
5

来一个复杂的例子,外层的 outerFunc 就起个迷惑目的:

function outerFunc() {
  console.log(this) // { x: 1 }

  function func() {
    console.log(this) // Window
  }

  func()
}

outerFunc.bind({ x: 1 })()
1
2
3
4
5
6
7
8
9
10
11

# 不在函数里--全局作用域中

不在函数中的场景,可分为浏览器的 <script /> 标签里,或 Node.js 的模块文件里。

  1. <script /> 标签里,this 指向 Window。
  2. 在 Node.js 的模块文件里,this 指向 Module 的默认导出对象,也就是 module.exports。

# 非严格模式

严格模式是在 ES5 提出的。在 ES5 规范之前,也就是非严格模式下,this 不能是 undefined 或 null。所以**在非严格模式下,通过上面七步口诀,如果得出 this 指向是 undefined 或 null,那么 this 会指向全局对象。**在浏览器环境中全局对象是 Window,在 Node.js 环境中是 Global。

例如下面的代码,在非严格模式下,this 都指向全局对象:

function a() {
  console.log("function a:", this)
  ;(() => {
    console.log("arrow function: ", this)
  })()
}

a()

a.bind(null)()

a.bind(undefined)()

a.bind().bind(2)()

a.apply()

// function a: Window {}
// arrow function: Window {}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

在严格模式下,则会输出 undefined / null。

七步口诀在严格模式下和非严格模式下都是完备的,只是在非严格模式下 null 或 undefined 会被转换为全局对象。所以没有将这点列入口诀中。

Last Updated: 2023/04/22, 23:39:26
彩虹
周杰伦