任何足夠先進的技術都和魔法無異。this 關鍵字是 JavaScript 中最複雜的機制之一,搞懂它很重要。javascript
this 是 JavaScript 中的關鍵字,在常見的面嚮對象語言中都有 this 的身影,相較下 JavaScript 中的 this 比較特殊,特殊在它會在執行期間動態改變指向。this 通常定義在函數中,若是按英文解釋,很容易產生誤解,this 既不指向函數自身,也不指向函數的詞法做用域。它在運行時進行綁定,它指向什麼徹底取決於函數在哪裏被調用。java
this 提供了一種更優雅的方式來隱式「傳遞」一個對象引用,讓咱們能夠避免顯示傳遞上下文對象引發的代碼混亂,所以能夠將 API 設計 得更加簡潔而且易於複用。數組
咱們廣泛知道的規則是,誰調用 this , this 指向誰。但這只是下面要講的規則中的一條。瀏覽器
咱們在寫原生 JS 時,會直接定義一個函數,而後直接進行獨立的函數調用,此時應用默認綁定規則。
舉例說明:安全
function run(){
console.log(this.a);
}
var a = 1;
run(); // 1
複製代碼
瀏覽器環境下,在非嚴格模式中,var 在全局做用域中定義的變量會自動添加到 window 對象下,在全局環境下執行函數 run ,就理解爲 run 在 window 中被調用,此時的 run 函數是定義在 window 對象中的。上面講 this 指向什麼徹底取決於函數在哪裏被調用,因此此時 this 指向 window, window.a 天然輸出 1。
嚴格模式中,var 聲明的變量不會自動綁定到 window 對象下,同理 run 函數也不是定義在 window 對象中的, run() 獨立執行,執行 RHS 右查詢 this ,查無此值,給到 undefined , undefined.a 天然報錯,且報錯類型爲 TypeError ,類型錯誤,由於 undefined 不是一個對象。app
這就是咱們常說的,誰調用 this , this 指向誰。
舉例說明:函數
function run(){
console.log(this.a);
}
var obj = {
a: 1,
run
}
obj.run(); // 1
複製代碼
注意: 隱式丟失的發生,就是咱們常說的 this 丟失,多發生在函數賦值。
舉例說明:post
function run() {
console.log( this.a );
}
var obj = {
a: 2,
run
};
var fn = obj.run; // 函數別名!
var a = "welcome"; // a 是全局對象的屬性
fn(); // "welcome"
複製代碼
你產生的困惑發生於var fn = obj.run;
賦值語句,很好理解,不要把問題複雜化。正如咱們所知的,JS 中函數即對象,obj.run
表明對run
函數對象的引用,但此時執行的是賦值語句,賦值語句右側通常執行 RHS 右查詢,即查詢具體的值,因此此時fn
直接指向run
函數自己,fn()
的執行等同於直接執行run()
,因此你的疑惑天然消除。
再看一個例子:ui
function run() {
console.log( this.a );
}
var obj = {
a: 2,
run
};
var a = "welcome"; // a 是全局對象的屬性
setTimeout( obj.run, 100 ); // "welcome"
複製代碼
setTimeout
函數第一個參數爲函數,咱們給它起個名字fn
,參數obj.run
的傳入發生隱式賦值fn = obj.run
,講到這裏,再結合上面,你該明白了。this
call、apply、bind 實現的綁定咱們稱爲實現顯示綁定,這裏不作過多解釋。
解釋一個咱們經常忽略的可選參數,看下面一段代碼:
function run(param) {
console.log( param, this.id );
}
var obj = {
id: "welcome"
};
[1, 2, 3].forEach( run, obj ); // 1 "welcome" 2 "welcome" 3 "welcome"
複製代碼
這裏數組的forEach()
方法,第二可選參數傳遞給函數的值通常用作 "this" 值。 若是這個參數爲空, "undefined" 會傳遞給 "this" 值。
咱們都知道 new 一個函數,函數內部的 this 指向新生成的實例,但這是爲何呢?咱們須要知道 new 的過程當中發生了什麼,固然咱們這裏不會講開闢什麼堆棧空間之類的,這不在咱們的討論範圍。
new 就是一個能夠調用普通函數的操做符,使用 new 來調用函數,或者說發生構造函數調用時,會自動執行下面的操做:
建立(或者說構造)一個全新的對象。
這個新對象會被執行 [[ 原型 ]] 鏈接。
這個新對象會綁定到函數調用的 this。
若是函數沒有返回其餘對象,那麼 new 表達式中的函數調用會自動返回這個新對象。
new綁定 > 顯示綁定 > 隱式綁定 > 默認綁定
因此咱們見到 this ,該根據此優先級規則來判別。
一些狀況下,咱們會在顯示綁定的時候傳入 null,例如Math.max.apply(null,[1,2,3])
,咱們並不關心傳入的 this ,只是傳入一個佔位置,此時應用默認綁定。 舉個例子:
function run() {
console.log( this.a );
}
var a = 1;
run.call(null); // 1
複製代碼
這裏建議傳入一個更安全的對象Object.create(null)
,Object.create(null)
建立的對象與{}
的區別在於,{}
上面還會有Object
原型上的toString()
等方法,而Object.create(null)
什麼都沒有。我喜歡用窮徒四壁來形容{}
,那麼Object.create(null)
對比之下就是連四壁也沒有。
咱們都知道箭頭函數沒有本身的 this,它的 this 來自於外層做用域,也由於箭頭函數沒有本身的 this 這一特性,它不能被用做構造函數。 箭頭函數不使用上面 this 的四種標準規則,函數執行時,它會捕獲外層做用域的 this ,一經綁定再也沒法修改,而且高於 new 綁定。
文章更多的內容是來自於《你不知道的JavaScript》上卷,固然加了本身的一些理解。沒時間去看書的同窗,不妨看看,查漏補缺。
學 React 的同窗看完,能夠再看下這篇 <理解:爲何React事件處理中要綁定this> ,鞏固下對 this 的理解。