this 其實是在函數被調用時發生的綁定,它指向什麼徹底取決於函數在哪裏被調用
app
當一個函數被調用時,會建立一個活動記錄(有時候也稱爲執行上下文)。這個記錄會包 含函數在哪裏被調用(調用棧)、函數的調用方法、傳入的參數等信息。this 就是記錄的 其中一個屬性,會在函數執行的過程當中用到
函數
1:指向自身oop
例如:咱們想要記錄一下函數 foo 被調用的次數,思考如下代碼this
console.log 語句產生了 4 條輸出,證實 foo(..) 確實被調用了 4 次,可是 foo.count 仍然 是 0。顯然從字面意思來理解 this 是錯誤的。 cdn
執行 foo.count = 0 時,的確向函數對象 foo 添加了一個屬性 count。可是函數內部代碼 this.count 中的 this 並非指向那個函數對象,因此雖然屬性名相同,根對象卻並不相 同。
對象
解決:強制this指向foo對象
blog
2:它的做用域
繼承
第二種常見的誤解是,this 指向函數的做用域。這個問題有點複雜,由於在某種狀況下它 是正確的,可是在其餘狀況下它倒是錯誤的。 ip
須要明確的是,this 在任何狀況下都不指向函數的詞法做用域。在 JavaScript 內部,做用 域確實和對象相似,可見的標識符都是它的屬性。可是做用域「對象」沒法經過 JavaScript 代碼訪問,它存在於 JavaScript 引擎內部。作用域
這段代碼中的錯誤不止一個。雖然這段代碼看起來好像是咱們故意寫出來的例子,可是實 際上它出自一個公共社區中互助論壇的精華代碼。這段代碼很是完美(同時也使人傷感) 地展現了 this 多麼容易誤導人。
首先,這段代碼試圖經過 this.bar() 來引用 bar() 函數。這是絕對不可能成功的,咱們之 後會解釋緣由。調用 bar() 最天然的方法是省略前面的 this,直接使用詞法引用標識符。
此外,編寫這段代碼的開發者還試圖使用 this 聯通 foo() 和 bar() 的詞法做用域,從而讓 bar() 能夠訪問 foo() 做用域裏的變量 a。這是不可能實現的,你不能使用 this 來引用一 個詞法做用域內部的東西.
每當你想要把 this 和詞法做用域的查找混合使用時,必定要提醒本身,這是沒法實現的。
首先要明確this的調用位置
最重要的是要分析調用棧(就是爲了到達當前執行位置所調用的全部函數)。咱們關心的 調用位置就在當前正在執行的函數的前一個調用中。
下面咱們來看看到底什麼是調用棧和調用位置:
function baz() {
// 當前調用棧是:baz
// 所以,當前調用位置是全局做用域
console.log( "baz" );
bar(); // <-- bar 的調用位置
}
function bar() {
// 當前調用棧是 baz -> bar // 所以,當前調用位置在 baz 中
console.log( "bar" );
foo(); // <-- foo 的調用位置
}
function foo() {
// 當前調用棧是 baz -> bar -> foo
// 所以,當前調用位置在 bar 中
console.log( "foo" );
}
baz(); // <-- baz 的調用位置
1:默認綁定
首先要介紹的是最經常使用的函數調用類型:獨立函數調用。能夠把這條規則看做是沒法應用 其餘規則時的默認規則。
思考一下下面的代碼:
function foo() {
console.log( this.a );
}
var a = 2;
foo(); // 2
在代碼中,foo() 是直接使用不帶任何修飾的函數引用進行調用的,所以只能使用 默認綁定,沒法應用其餘規則,所以 this 指向全局對象。
注:若是使用嚴格模式(strict mode),那麼全局對象將沒法使用默認綁定,所以 this 會綁定 到 undefined
2:隱式綁定
隱式綁定主要是考慮 調用位置是否有上下文對象,或者說是否被某個對象擁有或者包 含,不過這種說法可能會形成一些誤導。
思考下面的代碼:
function foo() {
console.log( this.a );
}
var obj = { a: 2, foo: foo };
obj.foo(); // 2
當 foo() 被調用時,它的落腳點確實指向 obj 對象。當函數引 用有上下文對象時,隱式綁定規則會把函數調用中的 this 綁定到這個上下文對象。由於調 用 foo() 時 this 被綁定到 obj,所以 this.a 和 obj.a 是同樣的。
注:對象屬性引用鏈中只有最頂層或者說最後一層會影響調用位置。
舉例來講:
function foo() {
console.log( this.a );
}
var obj2 = { a: 42, foo: foo };
var obj1 = { a: 2, obj2: obj2 };
obj1.obj2.foo(); // 42
隱式丟失問題
一個最多見的 this 綁定問題就是被隱式綁定的函數會丟失綁定對象,也就是說它會應用默認綁定,從而把 this 綁定到全局對象或者 undefined 上,取決因而否是嚴格模式。
思考下面的代碼:
function foo() {
console.log( this.a );
}
var obj = { a: 2, foo: foo };
var bar = obj.foo; // 函數別名!
var a = "oops, global"; // a 是全局對象的屬性
bar(); // "oops, global"
雖然 bar 是 obj.foo 的一個引用,,可是實際上,它引用的是 foo 函數自己,所以此時的 bar() 實際上是一個不帶任何修飾的函數調用,所以應用了默認綁定。
一種更微妙、更常見而且更出乎意料的狀況發生在傳入回調函數時:
function foo() {
console.log( this.a );
}
function doFoo(fn) {
// fn 其實引用的是 foo
fn(); // <-- 調用位置! }
var obj = { a: 2, foo: foo };
var a = "oops, global"; // a 是全局對象的屬性
doFoo( obj.foo ); // "oops, global"
參數傳遞其實就是一種隱式賦值,所以咱們傳入函數時也會被隱式賦值,因此結果和上一 個例子同樣。
若是把函數傳入語言內置的函數而不是傳入你本身聲明的函數,會發生什麼呢?結果是一 樣的,沒有區別:
function foo() {
console.log( this.a );
}
var obj = { a: 2, foo: foo };
var a = "oops, global"; // a 是全局對象的屬性
setTimeout( obj.foo, 100 ); // "oops, global"
JavaScript 環境中內置的 setTimeout() 函數實現和下面的僞代碼相似:
function setTimeout(fn,delay) {
// 等待 delay 毫秒
fn(); // <-- 調用位置!
}
3:顯式綁定
使用call() apply() bind()強行改變this的指向
4:new綁定
使用 new 來調用函數,或者說發生構造函數調用時,會自動執行下面的操做
1. 建立(或者說構造)一個全新的對象。
2. 這個新對象會被執行 [[ 原型 ]] 鏈接。
3. 這個新對象會綁定到函數調用的 this。
4. 若是函數沒有返回其餘對象,那麼 new 表達式中的函數調用會自動返回這個新對象。
思考下面的代碼:
function foo(a) {
this.a = a;
}
var bar = new foo(2);
console.log( bar.a ); // 2
使用 new 來調用 foo(..) 時,咱們會構造一個新對象並把它綁定到 foo(..) 調用中的 this 上。new 是最後一種能夠影響函數調用時 this 綁定行爲的方法,咱們稱之爲 new 綁定。
如今咱們能夠根據優先級來判斷函數在某個調用位置應用的是哪條規則。
能夠按照下面的 順序來進行判斷:
1. 函數是否在 new 中調用(new 綁定)?
若是是的話 this 綁定的是新建立的對象。
var bar = new foo()
2. 函數是否經過 call、apply(顯式綁定)或者硬綁定調用?
若是是的話,this 綁定的是 指定的對象。
var bar = foo.call(obj2)
3. 函數是否在某個上下文對象中調用(隱式綁定)?若是是的話,this 綁定的是那個上 下文對象。
var bar = obj1.foo()
4. 若是都不是的話,使用默認綁定。若是在嚴格模式下,就綁定到 undefined,不然綁定到 全局對象。
var bar = foo()
就是這樣。對於正常的函數調用來講,理解了這些知識你就能夠明白 this 的綁定原理了。 不過……凡事總有例外
1:被忽略的this
若是你把 null 或者 undefined 做爲 this 的綁定對象傳入 call、apply 或者 bind,這些值 在調用時會被忽略,實際應用的是默認綁定規則:
function foo() {
console.log( this.a );
}
var a = 2;
foo.call( null ); // 2
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
賦值表達式 p.foo = o.foo 的返回值是目標函數的引用,所以調用位置是 foo() 而不是 p.foo() 或者 o.foo()。根據咱們以前說過的,這裏會應用默認綁定。
3:箭頭函數
咱們以前介紹的四條規則已經能夠包含全部正常的函數。
可是 ES6 中介紹了一種沒法使用 這些規則的特殊函數類型:箭頭函數。
箭頭函數並非使用 function 關鍵字定義的,而是使用被稱爲「胖箭頭」的操做符 => 定 義的。
箭頭函數不使用 this 的四種標準規則,而是根據外層(函數或者全局)做用域來決 定 this。
function foo() {
// 返回一個箭頭函數
return (a) => {
//this 繼承自 foo()
console.log( this.a );
};
}
var obj1 = { a:2 };
var obj2 = { a:3}
var bar = foo.call( obj1 );
bar.call( obj2 ); // 2, 不是 3!
foo() 內部建立的箭頭函數會捕獲調用時 foo() 的 this。
因爲 foo() 的 this 綁定到 obj1, bar(引用箭頭函數)的 this 也會綁定到 obj1,箭頭函數的綁定沒法被修改。(new 也不 行!)
摘自 : 你不知道的JavaScript(上卷)