this 在实际的开发中经常出现,但是许多人对于 this 的指向常常无法进行准确的判断,今天就带大家了解下 this 的指向问题。
this 指向函数自身
function foo() {this.count++}foo.count = 0for (let i = 0; i < 3; i += 1) {foo()}console.log(foo.count)
上述例子中,foo 函数希望用自身的属性 count 记录自己被调用的次数。假设 this 指向函数自身的话,那么最终控制台打印出的结果应该是 3,但是当你实际运行完代码时会发现打印出的结果仍然是 0,这就充分的证明了 this 指向函数自身是 错误 的。
this 指向函数的词法作用域
上面说了两点 this 指向的错误理解,那 this 究竟指向什么呢?
this 实际上是在函数被 调用 时才发生绑定,它指向什么完全取决于函数的调用方式。用函数声明的代码去分析 this 的指向没有意义,接下来我们来看看 this 的绑定规则:
new 绑定
在 JavaScript 中也存在 new 操作符,常规是与函数搭配使用,我们先来讲解下当 new 与函数搭配进行函数调用时会发生什么:
- 创建一个全新的对象。
- 这个新对象的 [[Prototype]] 会与函数的
prototype进行关联。 - 这个新对象会绑定到函数调用的
this上。 - 如果函数没有返回其他对象,那么
new表达式中的函数调用会自动返回这个全新的对象。
function Person(name) {this.name = name}var person = new Person('O_c')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 指向的操作就称为 显示绑定。
function foo() {console.log(this.a)}var obj = {a: 2}foo.call(obj) // 2 // foo函数直接运行,他的this的绑定是指向obj
隐式绑定
当函数 引用 具有上下文对象时,函数调用时的 this 就会绑定到这个上下文对象上,这就是 隐式绑定 规则。
function foo() {console.log(this.a)}var obj = {a: 2,foo: foo}obj.foo() // 2
上述例子中,foo 被”包含”在上下文对象 obj 中,所以在函数调用时,隐式绑定规则就将 obj 对象绑定在了 foo 的 this 上。
隐式绑定丢失
隐式绑定丢失经常发生在 赋值 操作中,在进行赋值操作时因为丢失上下文对象,所以会导致 this 发生非预期的绑定现象,请看以下例子:
var a = 'global'function foo() {console.log(this.a)}var obj = {a: 2,foo: foo}var bar = obj.foobar(); // global
因为函数是引用类型,当我们将 obj.foo 赋值给 bar 时,实际上 bar 标识符就直接指向了 foo 函数的内存地址。这时我们调用 bar 就等同于直接调用 foo 函数,导致了上下文对象的丢失,所以最终控制台的打印结果就是 global 。
默认绑定
默认绑定 规则应用于独立函数调用的时候,也可以看作是无法应用上述三种规则时的默认规则,该规则会将函数调用的 this 绑定到全局对象上面。
但是该规则有一个需要注意的点就是,当 函数体 处于严格模式下时,this 会被绑定到 undefined:
function foo() {'use strict'console.log(this.a)}foo() // TypeError: Cannot read property 'a' of undefined
箭头函数
箭头函数 被单独拎出来说明是有原因的,因为箭头函数不遵循上述的四条规则,它是根据外层的作用域来决定 this 的,并且箭头函数的绑定是无法被修改的。
上述例子中,我们虽然是通过上下文对象 obj 进行的函数调用,但是箭头函数并没有受到隐式绑定规则的影响,所以控制台当中打印的结果为 2 。
小结
今天我们了解了 this 的绑定机制,要判断 this 的指向必须从函数的调用位置下手。找到函数调用之后可以顺序应用以下的四条规则进行判断:
- 由
new调用,绑定到新创建的对象。 - 由
call或者apply调用,绑定到指定的对象上。 - 由上下文对象调用,绑定到上下文对象上。
- 独立调用,在严格模式下绑定到
undefined,否则绑定到全局对象上。
当同个函数包含多种调用方式时,应用规则的 优先级 也是按照上述的排序。
最后记得 箭头函数 不应用上述的四条规则,它的 this 指向受所处的作用域决定
