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訂閱方便閱讀,感謝支持!