this关键字是JavaScript中最复杂的机制之一,是一个特别的关键字,被自动定义在所有函数的作用域中,但是相信很多JavaScript开发者并不是非常清楚它究竟指向的是什么。听说你很懂this,是真的吗?
普通函数
函数的this指向在函数定义的时候是不能确定的。只有在函数执行的时候才能确定函数的this指向谁
总结普通函数的绑定规则主要分为以下这几种:
默认绑定
默认绑定是在不能应用其他绑定规则的时候使用的默认规则, 通常是独立函数调用。
例子1:
1 | function getName() { |
在调用getName的时候,使用了默认绑定,this指向了全局对象。严格模式下,this指向undefined,``undefined`上没有this对象,会抛出错误。
这里例子是window调用了独立的函数,this就是指向window(非严格模式就是windom )
这里的例子可以这么看:
1 | function getName() { |
隐式绑定
函数的调用时在某个对象上触发的。即调用位置上存在上下文对象。典型的形式为 XXX.fun()。
例子2:
1 | function getName() { |
是对象person调用函数logName, 所以this指向person。
getName函数在外部声明, 严格来说并不属于person,但是在调用getName时,调用位置会使用person的上下文来引用函数,隐式绑定会把函数调用中的this(即此例getName函数中的this)绑定到这个上下文对象(即此例中的person).
注意:对象属性链中只有最后一层会影响到调用位置
例子3:
1 | function getName() { |
因为只有最后一层会确定this指向的是什么,不管有多少层,在判断this的时候,我们只关注最后一层,即此处的person。
隐式绑定有一个很大的缺陷,就是很容易丢失(即容易给我们造成误导,我们以为this指向的是什么,但是实际上并非如此)
例子4:
1 |
|
为什么结果是 shuliqi?
这是因为logName 直接指向了getName的引用, 跟person没关系。针对此类问题,我建议大家只需牢牢继续这个格式:XXX.fn();fn()前如果什么都没有,那么肯定不是隐式绑定。
除了上面这种丢失之外,隐式绑定的丢失是发生在回调函数中(事件回调也是其中一种),我们来看下面一个例子:
1 | function sayHi(){ |
结果为:
1 | Hello, Wiliam |
第一条输出很容易理解,
setTimeout的回调函数中,this使用的是默认绑定,非严格模式下,执行的是全局对象第二条输出是不是有点迷惑了?说好
XXX.fun()的时候,fun中的this指向的是XXX呢,为什么这次却不是这样了!Why?其实这里我们可以这样理解:
setTimeout(fn,delay){ fn(); },相当于是将person2.sayHi赋值给了一个变量,最后执行了变量,这个时候,sayHi中的this显然和person2就没有关系了。第三条虽然也是在
setTimeout的回调中,但是我们可以看出,这是执行的是person2.sayHi()使用的是隐式绑定,因此这是this指向的是person2,跟当前的作用域没有任何关系。
显示绑定
显示绑定就是通过apply,call,bind的方法, 显示的指定this所指的对象。
apply,call,bind的第一个参数都是该函数this所指的对象。 call,apply 是一样的, 都会立即执行,只是传参方式不同。call和apply都会执行对应的函数,而bind方法不会。apply的第二个参数是一个数组。
1 | function sayHi(){ |
结果:Hello, YvetteLau。因为明确将this绑定在了person上
那么,使用了硬绑定,是不是意味着不会出现隐式绑定所遇到的绑定丢失呢?显然不是这样的,不信,继续往下看。
1 | function sayHi(){ |
输出的结果是 Hello, Wiliam. 原因很简单,Hi.call(person, person.sayHi)的确是将this绑定到Hi中的this了。但是在执行fn的时候,相当于直接调用了sayHi方法(记住: person.sayHi已经被赋值给fn了,隐式绑定也丢了),没有指定this的值,对应的是默认绑定。
如果我们现在希望绑定不要丢失, 我们该怎么做? 很简单, 在调用fn的时候也是用显示绑定。
1 | function sayHi(){ |
此时,输出的结果为: Hello, YvetteLau,因为person被绑定到Hi函数中的this上,fn又将这个对象绑定给了sayHi的函数。这时,sayHi中的this指向的就是person对象。
如果我们将null或者是undefined作为this的绑定对象传入call、apply或者是bind,这些值在调用时会被忽略,实际应用的是默认绑定规则。
1 | function getName() { |
new 绑定
new的原理:
- 创建一个空对象
- 将空对象的原型指向构造函数的原型属性,从而继承构造原型上的方法(
newObj.__proto__ === Fn.prototype) - 将构造函数的
this指向新建的对象,并且执行构造函数的代码,从而获得构造函数的私有属性 - 最后看构造函数是否是返回一个对象,如果是直接返回该对象,如果不是, 则返回我们新建对象
1 | function Fn() { |
这里newFn对象子所以可以点name出来,是因为new关键字可以改变this指向。
实现new:
1 |
|
测试:
1 | function People(name) { |
普通函数的绑定优先级
我们知道了this有四种绑定规则,但是如果同时应用了多种规则,怎么办?
显然,我们需要了解哪一种绑定方式的优先级更高,这四种绑定的优先级为:
new绑定 > 显式绑定 > 隐式绑定 > 默认绑定
箭头函数
箭头函数this的定义: 箭头函数的this在定义的时候绑定。而不是在执行的时候绑定。它的this指向在定义的时候继承自外层第一个普通函数的this。
1 | var person = { |
内层箭头函数logName本身并没有this对象。它的this对象来自于外层作用域。logName函数的外层函数getName是一个普通的函数。 它是有this值的指向person对象。所以logName函数的this指向person对象。
