this
在实际的开发中经常出现,但是许多人对于 this
的指向常常无法进行准确的判断,今天就带大家了解下 this
的指向问题。
误解
this
是什么?this
是一种与函数相关的机制,它被自动的定义在函数的作用域中。我们在使用 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
的指向没有意义,接下来我们来看看 this
的绑定规则:
new 绑定
在 JavaScript 中也存在 new
操作符,常规是与函数搭配使用,我们先来讲解下当 new
与函数搭配进行函数调用时会发生什么:
- 创建一个全新的对象。
- 这个新对象的 [[Prototype]] 会与函数的
prototype
进行关联。 - 这个新对象会绑定到函数调用的
this
上。 - 如果函数没有返回其他对象,那么
new
表达式中的函数调用会自动返回这个全新的对象。
我们重点来关注1、3、4点,请看例子:
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
上述例子中,我们用 call(...)
方法显示的将 obj
对象绑定到 foo
函数调用的 this
上。
隐式绑定
当函数 引用 具有上下文对象时,函数调用时的 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
绑定到全局对象上面。
var a = 2
function foo() {
console.log(this.a)
}
foo() // 2
上述例子中,我们对 foo
函数进行了独立调用,所以它的 this
被默认绑定规则绑定到全局对象上面。
但是该规则有一个需要注意的点就是,当 函数体 处于严格模式下时,this
会被绑定到 undefined
:
var a = 2
function foo() {
'use strict'
console.log(this.a)
}
foo() // TypeError: Cannot read property 'a' of undefined
箭头函数
箭头函数 被单独拎出来说明是有原因的,因为箭头函数不遵循上述的四条规则,它是根据外层的作用域来决定 this
的,并且箭头函数的绑定是无法被修改的。
var a = 2;
var foo = () => {
console.log(this.a)
}
var obj = {
a: 4,
foo: foo
}
obj.foo() // 2
上述例子中,我们虽然是通过上下文对象 obj
进行的函数调用,但是箭头函数并没有受到隐式绑定规则的影响,所以控制台当中打印的结果为 2 。
小结
今天我们了解了 this
的绑定机制,要判断 this
的指向必须从函数的调用位置下手。找到函数调用之后可以顺序应用以下的四条规则进行判断:
- 由
new
调用,绑定到新创建的对象。 - 由
call
或者apply
调用,绑定到指定的对象上。 - 由上下文对象调用,绑定到上下文对象上。
- 独立调用,在严格模式下绑定到
undefined
,否则绑定到全局对象上。
当同个函数包含多种调用方式时,应用规则的 优先级 也是按照上述的排序。
最后记得 箭头函数 不应用上述的四条规则,它的 this
指向受所处的作用域决定。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!