js基础夯实-彻底搞懂this的指向问题
this 关键字是 JavaScript 中最复杂的机制之一,是一个特别的关键字,被自动定义在所有函数的作用域中.
为什么要搞懂this
- 面向oop编程需要知道什么是this
- 在各种function调用过程中可以清晰的弄懂为什么。
- 可以更好的理解大师们的代码
- 防止面试被咔嚓。。。(血的教训)
this是什么?
顾名思义,this就是一个指针,但是他并不是指向自身!而是指向调用函数的对象。(这句话很重要。也是本篇文章贯穿始终的主旨。)
this的指向判定标准
前人的判定
- 函数是否是
new
调用,如果是,则指向new
创建的实例。 - 函数是否是bind方法返回的?如果是,则this指向指定对象。
- 函数是否是通过
apply/call
调用的?如果是,则this指向指定对象。 - 是否作为对象的方法调用? 如果是,则this指向该对象。
- this指向全局。
为什么这样判定?
为什么通过这五种方式来判定呢?我们首先要知道this的绑定规则有哪些?
- 默认绑定
- 隐式绑定
- 显示绑定
- new 绑定
下面我们来分别挨个说明每个规则,来验证我们上述的判定标准。
默认绑定
在不能应用其他绑定规则时使用的默认规则,通常是独立函数调用。
function getName(){ |
在调用getName
的时候,应用了默认绑定,在非严格模式下this
指向全局对象,严格模式下this
是undefined
,
- 在浏览器模式下
this
指向了全局对象window
,所以运行结果是HellomySelf
- 在node环境下
this
指向的全局对象是node-runtime
在该文章中我们主要讨论浏览器中的表现。
隐式绑定
函数的调用存在上下文关系,典型案例如abc.foo():
var obj = { |
执行结果是a
;那如果我把sayHi的定义提升到外边,如下所示:
function h(){ |
执行结果跟上面是一样的。这是因为在函数sayHi
调用的时候,此时的上下文对象属于person
,隐式绑定会将函数中的this
绑定到person
这个上下文对象上
需要注意的是:在嵌套对象的调用时,只有离
this
最近的那一层会影响到调用位置。
function h(){ |
结果是obj1
,这是因为只有执行到obj1.func
的时候,才能确定函数h
中this
指向的上下文对象为obj1
.
隐式绑定的丢失主要体现在两个方面:
- 函数重新赋值,如下面代码所示:
function h(){
console.log(this.tag)
}
var obj1 = {
tag:'obj1',
func:h
}
var tag = 'window'
var callh = obj1.func;
callh(); //window
这是因为callh
直接指向了函数h
的引用,在执行callh
的时候上下文对象为document.window
- 在回掉函数和事件回调中也会丢失绑定,如下代码所示
function h(){
console.log(this.tag)
}
var obj1 = {
tag:'obj1',
func:function(){
setTimeout(function(){
console.log(this.tag)
})
}
}
var obj2 = {
tag:'obj2',
func:h
}
var tag = 'window'
obj1.func(); // window
setTimeout(obj2.func,200); // window
setTimeout(function(){
obj2.func()
},200) //obj2
- 第一个输出是因为命中了默认绑定,
this
指向了document.window
- 第二个输出是因为
obj2.func
赋值给了setTimeout
的回调入参之后又执行这个入参,所以隐式绑定丢失,this
指向了document.window
- 第三条输出是因为命中了隐式绑定,
this
指向了obj2
显式绑定
通过 call,apply,bind
的方式,显式的指定this
所指向的对象。call,apply,bind
的第一个参数,就是对应函数的this
所指向的对象。call
和apply
的作用一样,只是传参方式不同。call
和apply
都会执行对应的函数,而bind
方法不会。
function h(){ |
因为obj1.func
作为入参传入的时候,已经丢失了隐式绑定的this
new
关键字绑定
使用new
来调用函数,会自动执行下面的操作:
- 创建一个新对象
- 将构造函数的作用域赋值给新对象,即
this
指向这个新对象 - 执行构造函数中的代码
- 返回新对象
绑定优先级
new 绑定 > 显式绑定 > 隐式绑定 > 默认绑定
绑定例外
如果我们将null
或者是undefined
作为this
的绑定对象传入call、apply、bind
, 这些值在调用时会被忽略,实际应用的是默认绑定规则。
箭头函数
箭头函数是 ES6 中新增的,它和普通函数有一些区别,箭头函数没有自己的 this,它的 this 继承于外层代码库中的 this。箭头函数在使用时,需要注意以下几点:
- 函数体内的 this 对象,继承的是外层代码块的 this。
- 不可以当作构造函数,也就是说,不可以使用 new 命令,否则会抛出一个错误。
- 不可以使用 arguments 对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
- 不可以使用 yield 命令,因此箭头函数不能用作 Generator 函数。
- 箭头函数没有自己的 this,所以不能用 call()、apply()、bind() 这些方法去改变 this 的指向.
总结
1 . 函数是否在 new 中调用 (new 绑定),如果是,那么 this 绑定的是新创建的对象。
2 . 函数是否通过 call,apply 调用,或者使用了 bind(即硬绑定),如果是,那么 this 绑定的就是指定的对象。
3 . 函数是否在某个上下文对象中调用 (隐式绑定),如果是的话,this 绑定的是那个上下文对象。一般是 obj.foo()。
4 . 如果以上都不是,那么使用默认绑定。如果在严格模式下,则绑定到 undefined,否则绑定到全局对象。
5 . 如果把 Null 或者 undefined 作为 this 的绑定对象传入 call、apply 或者 bind,这些值在调用时会被忽略,实际应用的是默认绑定规则。
6 . 如果是箭头函数,箭头函数的 this 继承的是外层代码块的 this。
参考