this 綁定是面試題常考的類型,同時,它和原型鏈、閉包結合在一塊兒,能夠實現不少複雜的功能。本文將 this 綁定涉及相關知識作了一個概括整理,固然,也從中收穫了不少。javascript
這裏直接列出 this 綁定的四大規則,這些規則都是《你不知道的JavaScript》一書中提到的關鍵詞,我的以爲比較好理解。由於這裏只涉及常識性的介紹,若是已經瞭解的話能夠快速跳過,進入下一小節。若是你還不熟悉,可能會碰到不少陌生又讓你心癢的概念。這裏建議你先暫時放下,我會在後文詳細說明,或貼上我以爲不錯的博客。html
又能夠叫函數調用。正如其名,它指的是在沒有其它規則的狀況下默認使用的綁定規則,通常就是直接調用函數的狀況。在通常狀況下,默認綁定的對象是全局對象 window
;當函數內部處於嚴格模式下時,它綁定的是 undefined
。java
case1.1.1面試
// 這裏是一般狀況下的默認綁定
var a = 0;
function foo() {
var a = 10;
console.log(「a=」 + this.a); // window.a
}
foo();
// a=0
複製代碼
case1.1.2segmentfault
// 嚴格模式下的默認綁定(注意 use strict 位置)
var a = 0;
function foo() {
"use strict";
var a = 10;
console.log(「a=」 + this.a); // window.a
}
foo();
// a=0
複製代碼
能夠看到,這裏的 this.a
綁定的對象是全局對象。bash
隱式綁定又能夠叫對象方法調用,即做爲對象方法使用。既然是對象調用,那麼就少不了對象的使用。具體的使用狀況以下:數據結構
case1.2閉包
var a = 0;
var b = {
a: 10,
foo: function() {
var a = 20;
console.log("a=" + this.a); // b.a
}
};
b.foo();
// a=10
複製代碼
其實挺好理解,這裏面 this 的綁定對象是 b。可是,使用隱式綁定時,咱們仍然很容易踩到一些坑,這個坑後文會提到。app
顯式綁定看似和上面的隱式綁定相對應,其實沒太大關係。它又能夠叫作 apply/call 調用,其實就是使用了 JavaScript 中很強大的兩個方法。這兩個方法使用效果,簡而言之是強行爲所指定函數綁定所指定對象。這裏貼上相關的補充連接,不過即便你不看也不影響瀏覽本文:JS 中的 call、apply、bind 方法詳解函數
假如你差很少了解清楚了,看看下面的例子。
case1.3
var a = 0;
var b = {
a: 10
};
var c = {
a: 20, // c.a === 20
foo: function() {
console.log("a=" + this.a);
}
};
c.foo.call(b); // 爲foo的this指定綁定對象b
// a=10
複製代碼
上面是顯式綁定的一個例子。能夠看到,首先 foo
是由 c
做爲對象方法進行調用的,而後使用了 call
方法顯式綁定到 b
上,最後的輸出結果就如上。你們也能夠猜到,這裏顯式綁定的優先級高於隱式綁定,至於其它幾種組合方式,也等到後文再詳細介紹。
也叫構造函數調用。不過,對於 JS 的構造函數,不熟悉的同窗千萬別將之與 C/Java 之類的構造函數相提並論,二者實質是徹底不一樣的(雖然下例看起來好像很類似)!這裏先上代碼,更深層次的內容後文再說~
case1.4
var a = 0;
function Foo() {
this.a = 10; // 看成爲構造函數時,這裏能夠看做 a: 10 的聲明形式
}
var foo = new Foo();
console.log(foo.a); // foo 此時是一個對象而不是函數
// 10
console.log(Foo.a); // 將 Foo 做爲對象調用試試看?
// undefined
複製代碼
看起來和上面那三種毫無聯繫,實際上它的原理與顯式綁定緊密相關。若是你看到這裏還沒懵的話,那就接着下一節看吧~(若是懵的話,能夠將上面四個示例在 codepen 敲一遍)
上面總結的四個綁定規則,在實際應用中有各類各樣的變式出現。若是對這些意外狀況進行仔細分析,也能幫助咱們更好地理解 JS。
上文其實部分涉獵了綁定優先級的問題。很顯然,默認綁定的優先級必定是最低的;在 case1.3 中,咱們又知道了顯式綁定優先級高於隱式綁定。可是,咱們如何肯定 new 綁定的優先級呢?
在 case1.4 中,咱們瞭解到構造函數 new 返回的是一個對象,但須要注意的是,這個返回對象是新構造的,即不與原來的任何函數或對象產生聯繫。怎麼理解這一點呢?看下面這個例子
case2.2.1
var obj = {
a: 10,
foo: function(a) {
this.a = a;
}
}
obj.foo(20);
console.log(obj.a);
// 20
var bar = new obj.foo(30);
console.log(bar.a);
// 30
console.log(obj.a);
// 20
複製代碼
case2.2.1 能夠印證以前所說的。同時,經過這個例子咱們也能夠看到,對於 bar
接受的返回值來講,new 綁定的優先級高於隱式綁定。
那麼,new 綁定與顯式綁定的優先級如何對比呢?《你不知道的 JavaScript》一書提到一個例子,但這個例子我的以爲還不足以說明問題,因此這裏將原文的例子進行了改進和補充。
case2.2.2
function foo(a) {
this.a = a;
}
var obj = {};
var bar = foo.bind(obj); // bind 和 apply/call 功能相似,但返回的是一個函數引用
bar(2); // 執行了 this.a = 2
console.log(obj.a);
// 2
var baz = new bar(3); // 將顯式綁定返回的函數做爲構造函數使用,看看結果怎樣?
console.log(obj.a); // 前面提到的,構造函數返回的對象不影響原來的對象或函數
// 2
console.log(baz.a); // 這裏說明,new 綁定的結果不受顯式綁定的影響
// 3
bar(4); // 假如再執行一次呢?若是無效,說明 new 綁定優先級比較高
console.log(obj.a); // 這裏說明,兩種綁定實際上互不干擾
// 4
複製代碼
其實上面繞來繞去的,理解問題的一個關鍵就是 new 綁定的原理究竟是什麼?這是一個寫起來又能撐一篇的問題,老慣例,貼出我認爲寫得比較好的博客。
這是《你不知道的 JavaScript》中提到的例子,這裏將之收集起來,作一個統一彙總。
case2.1.1
var b = {
a: 10,
foo: function () {
console.log(this.a);
}
}
var fn = b.foo;
fn();
// undefined
複製代碼
case2.1 中,fn 引用的是一個 foo 函數,而不是 b.foo 的對象方法。也就是說,在賦值操做後,foo 所處的環境是全局域而不是 b 的詞法做用域內。
case2.1.2
function foo() {
console.log(this.a);
}
var a = 2;
var o = {a: 3, foo: foo};
var p = {a: 4};
o.foo();
// 3
(p.foo = o.foo)();
// 2
複製代碼
case2.1.2 的緣由與 case2.1.1 是同樣的。看到此處,有些人可能對 JS 中奇怪的各類做用域產生疑惑,爲何一個賦值操做會改變所處做用域呢?
這裏涉及的問題實際上是 JS 的內存數據結構設計的問題。具體的能夠點下面連接,看完這個連接,相信你以前的一些疑惑都會獲得解答。
這篇文章攢了比較久,說實話本身想寫的不少點都還沒能寫出來。一方面是由於避免寫得太雜,因此有一些問題我會貼出博客,若是仍是不能解決能夠私信我;另外一方面也是能力有限,即便只是作一些整理性的工做,仍然耗費了我大量的時間,並且再拖下去也沒太大意義。因此,這篇我認爲還算有深度,勉強算原創的文章就寫到這吧。
今天是勞動節,做者沒有放假而是一下午肝了這篇,由於這算是上週欠下的了。同時,我計劃會在勞動節假期再更一篇,要寫哪些方向已經想好了,到時候會從下面兩條中挑一條(我但願我能挑戰好第二條,因此給個鼓勵吧~)