this
指针圈圈绕?this
绑定做用域的四种状况
new
操做符绑定this
的绑定产生了什么影响?this
指针圈圈绕?相比C++或者Java中的this
指针的概念而言,JavaScript中的this
指针更为 "灵活" ,C++或Java中的 this
在类定义时便成为了一个指向特定类实例的指针,可是JavaScript中的this
指针是能够动态绑定的,也就是说是依据上下文环境来指定this
到底指向谁。这样的机制给编程带来了很大的灵活性(以及趣味性),但这也致使了在JavaScript编程中若不明白this
指针的做用机制而滥用this
指针的话,经常会引起一些 "莫名其妙" 的问题。好比说,下面这段程序:javascript
1. let num = 10;
2. console.log(global.num);
3. global.num = 20;
4. console.log(this.num);
5. console.log(this === global);
6. function A(){
7. console.log(this.num++);
8. }
9. let obj = {
10. num : 30,
11. B : function(){
12. console.log(this.num++);
13. return () => console.log(this.num++);
14. }
15. }
16. A();
17. let b = obj.B;
18. b()();
19. obj.B();
20. b.apply(obj);
21. new A();
22. console.log(global.num);
复制代码
你能列出最终全部的输出吗?你能够先尝试着写一下,不要复制到VSCode中运行哦~ ,手动写出答案!写完先看一下最后面的答案,看你是否写对了。若是写对了说明你已经基本掌握了JavaScript中this
指针的机制 (PS:设定这里运行环境是node环境
) ;若是没有写对,那看完本文相信就能够对this
有一个基本清楚的认识了。java
相信你确定忍不住去看了答案了,或许答案看起来杂乱无章,这也是为何this
做为JavaScript中最复杂的机制之一常常被拿到面试中去考察JS的功底。如下内容可能须要花费8-10分钟时间,可是会让读者你受益不浅的,你的疑问也能够在下面的内容中获得解答!node
this
绑定做用域的四种状况在讲解this
绑定做用域的四种状况以前,咱们先要弄清楚一个问题。Node环境中的全局做用域和浏览器环境下的全局做用域有什么不一样?面试
这个问题很重要,由于这个异同,会致使一样的代码在Node环境和浏览器环境下的表现不尽相同。就好比咱们这里要讲的this
指针的指向会由于环境不一样而不一样。这个不一样体如今如下三点:编程
window
; 而Node中这个"等价"的全局对象是global
this
指向的就是window
对象; 可是Node环境下全局做用域中的this
和global
是分离的,this
指针指向一个空对象window
的属性;可是Node下全局做用域下的声明的变量不属于global
。由此,你即可知,上面代码中1-5的输出了,就像下面这样:浏览器
undefined // 1
undefined // 2
false // 3
复制代码
为了方便讲解,我给每一个输出编了号,咱们依次来看:微信
undefined
是由于Node的全局做用域上的变量并不会做为global
的属性,此时global.num
还没有赋值,因此是undefined
undefined
是由于Node中全局做用域中的this
并不指向global
,因此此时this.num
还没有赋值,因此也是undefined
false
也更加应证了 2 中的结论,Node
中全局做用域的this
与global
风马牛不相及【PS】上面我一直强调是全局做用域下的this
,为何呢?由于Node中在子做用域中的this
的行为和浏览器中是相仿的,基本一致闭包
下面咱们来说解JavaScript中this
绑定做用域的四种状况。app
先来讲第一种——默认绑定 ,咱们能够这样理解 默认绑定 ,this
指针在做用域内没有认领的对象时就会默认绑定到全局做用域的全局对象中去,在Node中就是会绑定到global对象上去。这是一个很形象的说法,虽然不严谨可是好理解,咱们看下面这几个例子,来讲明什么状况下,this
没有对象认领。函数
global.name = 'javascript';
(function A(){
this.name += 'this';
console.log(this.name);//输出 javascriptthis
})();
console.log(global.name);//输出 javascriptthis
复制代码
在函数A的做用域内,this
并无能够依靠的对象,因此this
指针便开启默认绑定模式,此时指向的是global
。
这里咱们有必要明确一个概念,有别于JavaScript中"一切皆为对象"的概念,虽然A
确实是一个Function
类型的对象 , 下面的例子可证实确实如此
function A(){}
console.log(A instanceof Function); //输出 true
console.log(A instanceof Object); //输出 true
复制代码
可是function A(){}
只是一个函数的声明,并无实例对象的产生,而this
是须要依托于一个存在的实例对象 , 若是使用new A()
则便有了实例对象,this
也就有了依托,有关new操做符绑定
在后面说。
明白了这一点,咱们来看一个更为复杂的例子:
global.name = 'javascript';
(function A(){
this.name += 'this';
return function(){
console.log(this.name);//输出 javascriptthis
}
})()();
console.log(global.name);//输出 javascriptthis
复制代码
这个例子中函数A
返回了一个匿名函数也能够叫闭包,咱们发现this
照样绑定在了global
上。这个例子是想告诉读者,默认绑定和做用域层级没有关系,只要是在做用域内this
找不到认领的实例对象,那就会启用默认绑定。
由此,你是否是能够知道开篇的例子中 6,7,8,16行的输出结果了?
20 //这里是后置自增运算,因此先输出后加一
复制代码
隐式绑定顾名思义没有显式的代表this
的指向,可是已经绑定的某个实例对象上去了。举个简单的例子,这个用法实际上是咱们最经常使用的:
global.name = 'javascript' ;
let obj = {
name : 'obj',
A : function(){
this.name += 'this';
console.log(this.name);
}
}
obj.A();//输出 objthis
console.log(global.name);//输出 javascript
复制代码
这个例子中函数A
的做用域内,this
总算是有对象认领了,这个对象就是obj
,因此this.name
指向的就是obj
中的name
,这种状况就叫作隐式绑定
隐式绑定虽然是咱们最经常使用的,也是相对好理解的一种绑定方式,可是确是四种绑定中最坑的一种,为何呢?由于,这种状况下this
一不当心就会找不到认领她的对象了,咱们称之为"丢失"。而在"丢失"的状况下,this
的指向会启用默认绑定。咱们看下面的例子;
global.name = 'javascript' ;
let obj = {
name : 'obj',
A : function(){
this.name += 'this';
console.log(this.name)
},
B : function(f){
this.name += 'this';
f();
},
C : function(){
setTimeout(function(){
console.log(this.name);
},1000);
}
}
let a = obj.A; // 1
a(); // 2
obj.B(function(){ // 3
console.log(this.name); // 4
}); // 5
obj.C(); // 6
console.log(global.name); // 7
复制代码
这里列出了三种"丢失"的状况:
obj的A函数
赋值给了a
,而后调用a()
,这时候函数的执行上下文发生了变化,至关因而全局做用域下的一个函数的执行,因此承接咱们上面所说,此时启用了默认绑定obj.B
传递一个Function
参数,而且在B
中f()
执行,这至关于一个B
中的当即执行函数,此时在this
所在做用域找不到认领的对象,因而启用默认绑定Node环境
和浏览器环境
下的结果是不同的,按照常理来讲,回调函数中的this
一样会由于丢失而启用默认绑定,在浏览器环境下确实如此。可是在node
中事情好像没那么简单,咱们先看看输出的结果,在作分析javascriptthis // 1-2行执行结果
javascriptthis // 3-5行执行结果
javascriptthis // 7行执行结果
undefined // 6行执行结果
复制代码
你会发现有一个值很扎眼,没错,就是undefined
,那为何setTimeout()
回调中的this
没有启用默认绑定呢?这里根据这篇博客作了一个猜测 : NodeJS 回调函数中的this ,我建议你看一看这篇博客
亦如fs.open()
回调同样,setTimeout()
函数会先初始化本身,那么此时回调函数做用域上就是存在实例对象了,只是这个对象咱们看不到而已,因此此时this.name
并未初始化,因此输出undefined
。为此我作了一个实验来证实,setTimeout()
的this
指向不等于global
function A(){
console.log(this === global);
}
A(); //输出 true
setTimeout(function(){
console.log(global === this);
},1000); // 输出 false
复制代码
由此,咱们能够知道,开篇例子中18,19行的输出即是:
21 // 隐式绑定丢失
22 // 箭头函数绑定上级做用域this指针,这个后面会讲
30 //隐式绑定
复制代码
接下来要讲的是硬绑定 , 这个比较简单,是经过JS的三个经常使用API来显式的实现绑定特定做用域,这三个API为
这三个API之间的关系非本篇关键,能够自行了解,本篇以
apply
为例
咱们知道JS函数的一大特色就是有 定义时上下文 、运行时上下文 以及 上下文可变 的概念,而apply
就是帮助咱们改变函数运行时上下文的环境,这种经过API显式指定某个函数执行上下文环境的绑定方式就是 硬绑定
咱们来看下面这个例子:
global.name = 'global';
function A(){
console.log(this.name);
}
let obj = {
name : 'obj'
}
A.apply(obj); //输出 obj
A.apply(global); //输出 global
复制代码
对,你应该懂了,什么叫硬绑定。就是无论函数你定义在哪里,这样使用了我这个API,你就能够随心所欲,绑定到任意做用域的对象上去,哪怕是global
都不带怕的,硬核API !!!
由此,你也能够获得开篇例子中20行输出结果应该是:
31 //obj.name在此以前被加了一次1,因此这里是31
复制代码
new
操做符绑定最后一种绑定方式是new
操做符绑定,这个也是JS中最经常使用的用法之一了,简单来讲就是经过new
操做符来实例化一个对象的过程当中发生了this
指针的绑定,这个过程是不可见的,是后台帮你完成了这一绑定过程。具体是什么过程呢?这里咱们就已开篇的例子为例吧
function A(){
console.log(this.num++);
}
new A(); //输出为 NaN
复制代码
NaN
是JSNumber
对象上的一个静态属性,意如其名"not a number",表示不是数字。这里new A()
实例化了一个对象,此时在A
的做用域里就用对象认领this
指针了,因此此时this
指向实例化对象,可是这个对象中num
属性并无初始化,所以是undefined
,而undefined
非数字却使用了++
运算,所以最终输出了NaN
既然this
的绑定有四种机制,那一定会出现机制冲突的状况,不要紧,其实从上面的讲解中你应该已经能隐约感受到这四种机制是有优先级存在的。好比,在new
操做符绑定的时候,就是由于new
绑定优先级高于默认绑定,因此this
指针指向的是新实例化的对象而不是全局对象global
。这里给出这四种绑定的优先级 :
new 操做符绑定 > 硬绑定 > 隐式绑定 > 默认绑定
这个关系仍是挺明显的,故不做例子阐述了。
this
的绑定产生了什么影响?快要结束了,再坚持一下,最后有必要说明如下ES6中的箭头函数对于this
指针绑定的影响,ES6中引入箭头函数是为了更优雅的书写函数,对于那些简单的函数咱们使用箭头函数代替原来的函数写法能够大大简化代码量并且看上去更加整洁优雅。也正是由于箭头函数的设计是为了简洁优雅,因此箭头函数除了简化代码表示之外,还简化了函数的行为。
new
来实例化对象this
指针,这不表明不能使用this
,箭头函数中的this
是继承自父级做用域上的this
,也就是说箭头函数中的this
绑定的是父级做用域内this
所指向的对象举个例子来说:
name = 'global';
this.name = 'this';
let obj = {
name : 'obj',
A : function(){
()=>{
console.log(this.name)
}
},
B :() => {
console.log(this.name)
}
}
obj.A(); //输出 obj
obj.B(); //输出 this
复制代码
这里或许obj.B()
输出让你疑惑,其实咱们开篇也讲了,全局做用域下this
和global
风马牛不相及,因此这里对应到父级做用域中this
对应的对象就是this
自己或者export
由此,开篇示例中18行的输出即可知晓了 :
21 // b() 所输出
22 // (b())()所输出
复制代码
这里有些绕,之因此最终this
绑定到了global
上,是分了两步
this
继承父级this
绑定到了obj
上this
默认绑定到了global
上undefined
undefined
false
20
21
22
30
31
NaN
23
复制代码
总算是写完了,写做的过程,笔者收获也很大,就好比Node中回调函数的this
指向问题我也没有想到,是经过实验才印证Node中回调函数中this
指向的是自身实例化的对象,这个工做一样不可见,后台完成了,就像new
同样。 但愿读者也能够获得收获!
下面是个人微信公众号,若是以为本篇文章你收获很大,能够关注个人微信公众号,我会同步文章,这样能够RSS订阅方便阅读,感谢支持!