this
在实际的开发中经常出现,但是许多人对于 this
的指向常常无法进行准确的判断,今天就带大家了解下 this
的指向问题。
this
指向函数自身
function foo() {
this.count++
}
foo.count = 0
for (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.foo
bar(); // 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
指向受所处的作用域决定