this 在实际的开发中经常出现,但是许多人对于 this 的指向常常无法进行准确的判断,今天就带大家了解下 this 的指向问题。

this 指向函数自身

  1. function foo() {
  2. this.count++
  3. }
  4. foo.count = 0
  5. for (let i = 0; i < 3; i += 1) {
  6. foo()
  7. }
  8. console.log(foo.count)

上述例子中,foo 函数希望用自身的属性 count 记录自己被调用的次数。假设 this 指向函数自身的话,那么最终控制台打印出的结果应该是 3,但是当你实际运行完代码时会发现打印出的结果仍然是 0,这就充分的证明了 this 指向函数自身是 错误 的。

this 指向函数的词法作用域

上面说了两点 this 指向的错误理解,那 this 究竟指向什么呢?

this 实际上是在函数被 调用 时才发生绑定,它指向什么完全取决于函数的调用方式。用函数声明的代码去分析 this 的指向没有意义,接下来我们来看看 this 的绑定规则:

new 绑定

在 JavaScript 中也存在 new 操作符,常规是与函数搭配使用,我们先来讲解下当 new 与函数搭配进行函数调用时会发生什么:

  1. 创建一个全新的对象。
  2. 这个新对象的 [[Prototype]] 会与函数的 prototype 进行关联。
  3. 这个新对象会绑定到函数调用的 this 上。
  4. 如果函数没有返回其他对象,那么 new 表达式中的函数调用会自动返回这个全新的对象。
  1. function Person(name) {
  2. this.name = name
  3. }
  4. var person = new Person('O_c')
  5. console.log(person.name) // O_c

上述例子中,new 操作符与 Person 函数搭配,在函数调用时会生成一个新的对象 person,并且 person 对象会绑定到函数调用的 this上,所以当执行 this.name = name 时,就是在执行 person.name = name

现在了解了 new 操作符可以影响函数调用的 this 绑定行为,这称之为 new 绑定。

显示绑定

在 JavaScript 中绝大多数的函数都拥有 call(...)apply(...) 这两个方法。它们的第一个参数是一个对象,它们会把这个对象绑定到函数调用的 this 上,这种直接指定 this 指向的操作就称为 显示绑定

  1. function foo() {
  2. console.log(this.a)
  3. }
  4. var obj = {
  5. a: 2
  6. }
  7. foo.call(obj) // 2 // foo函数直接运行,他的this的绑定是指向obj

隐式绑定

当函数 引用 具有上下文对象时,函数调用时的 this 就会绑定到这个上下文对象上,这就是 隐式绑定 规则。

  1. function foo() {
  2. console.log(this.a)
  3. }
  4. var obj = {
  5. a: 2,
  6. foo: foo
  7. }
  8. obj.foo() // 2

上述例子中,foo 被”包含”在上下文对象 obj 中,所以在函数调用时,隐式绑定规则就将 obj 对象绑定在了 foothis 上。

隐式绑定丢失

隐式绑定丢失经常发生在 赋值 操作中,在进行赋值操作时因为丢失上下文对象,所以会导致 this 发生非预期的绑定现象,请看以下例子:

  1. var a = 'global'
  2. function foo() {
  3. console.log(this.a)
  4. }
  5. var obj = {
  6. a: 2,
  7. foo: foo
  8. }
  9. var bar = obj.foo
  10. bar(); // global

因为函数是引用类型,当我们将 obj.foo 赋值给 bar 时,实际上 bar 标识符就直接指向了 foo 函数的内存地址。这时我们调用 bar 就等同于直接调用 foo 函数,导致了上下文对象的丢失,所以最终控制台的打印结果就是 global 。

默认绑定

默认绑定 规则应用于独立函数调用的时候,也可以看作是无法应用上述三种规则时的默认规则,该规则会将函数调用的 this 绑定到全局对象上面。

但是该规则有一个需要注意的点就是,当 函数体 处于严格模式下时,this 会被绑定到 undefined:

  1. function foo() {
  2. 'use strict'
  3. console.log(this.a)
  4. }
  5. foo() // TypeError: Cannot read property 'a' of undefined

箭头函数

箭头函数 被单独拎出来说明是有原因的,因为箭头函数不遵循上述的四条规则,它是根据外层的作用域来决定 this 的,并且箭头函数的绑定是无法被修改的。

上述例子中,我们虽然是通过上下文对象 obj 进行的函数调用,但是箭头函数并没有受到隐式绑定规则的影响,所以控制台当中打印的结果为 2 。

小结

今天我们了解了 this 的绑定机制,要判断 this 的指向必须从函数的调用位置下手。找到函数调用之后可以顺序应用以下的四条规则进行判断:

  1. new 调用,绑定到新创建的对象。
  2. call 或者 apply 调用,绑定到指定的对象上。
  3. 由上下文对象调用,绑定到上下文对象上。
  4. 独立调用,在严格模式下绑定到 undefined ,否则绑定到全局对象上。

当同个函数包含多种调用方式时,应用规则的 优先级 也是按照上述的排序。

最后记得 箭头函数 不应用上述的四条规则,它的 this 指向受所处的作用域决定