你不知道的JS系列——全面解析this

前言


任何足夠先進的技術都和魔法無異。this 關鍵字是 JavaScript 中最複雜的機制之一,搞懂它很重要。javascript

1、this 是什麼?


thisJavaScript 中的關鍵字,在常見的面嚮對象語言中都有 this 的身影,相較下 JavaScript 中的 this 比較特殊,特殊在它會在執行期間動態改變指向。this 通常定義在函數中,若是按英文解釋,很容易產生誤解,this 既不指向函數自身,也不指向函數的詞法做用域。它在運行時進行綁定,它指向什麼徹底取決於函數在哪裏被調用。java

2、爲何要用 this


this 提供了一種更優雅的方式來隱式「傳遞」一個對象引用,讓咱們能夠避免顯示傳遞上下文對象引發的代碼混亂,所以能夠將 API 設計 得更加簡潔而且易於複用。數組

3、4條綁定規則


咱們廣泛知道的規則是,誰調用 thisthis 指向誰。但這只是下面要講的規則中的一條。瀏覽器

一、默認綁定

咱們在寫原生 JS 時,會直接定義一個函數,而後直接進行獨立的函數調用,此時應用默認綁定規則。
舉例說明:安全

function run(){
    console.log(this.a);
}
var a = 1;
run();  // 1
複製代碼

瀏覽器環境下,在非嚴格模式中,var 在全局做用域中定義的變量會自動添加到 window 對象下,在全局環境下執行函數 run ,就理解爲 runwindow 中被調用,此時的 run 函數是定義在 window 對象中的。上面講 this 指向什麼徹底取決於函數在哪裏被調用,因此此時 this 指向 window, window.a 天然輸出 1
嚴格模式中,var 聲明的變量不會自動綁定到 window 對象下,同理 run 函數也不是定義在 window 對象中的, run() 獨立執行,執行 RHS 右查詢 this ,查無此值,給到 undefined , undefined.a 天然報錯,且報錯類型爲 TypeError ,類型錯誤,由於 undefined 不是一個對象。app

默認綁定下, thiswindowundefined 兩者其一,取決因而否是嚴格模式。

二、隱式綁定

這就是咱們常說的,誰調用 thisthis 指向誰。
舉例說明:函數

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綁定

咱們都知道 new 一個函數,函數內部的 this 指向新生成的實例,但這是爲何呢?咱們須要知道 new 的過程當中發生了什麼,固然咱們這裏不會講開闢什麼堆棧空間之類的,這不在咱們的討論範圍。
new 就是一個能夠調用普通函數的操做符,使用 new 來調用函數,或者說發生構造函數調用時,會自動執行下面的操做:

建立(或者說構造)一個全新的對象。

這個新對象會被執行 [[ 原型 ]] 鏈接。

這個新對象會綁定到函數調用的 this。

若是函數沒有返回其餘對象,那麼 new 表達式中的函數調用會自動返回這個新對象。

4、優先級

new綁定 > 顯示綁定 > 隱式綁定 > 默認綁定

因此咱們見到 this ,該根據此優先級規則來判別。

5、被忽略的 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)對比之下就是連四壁也沒有。

6、箭頭函數下的 this

咱們都知道箭頭函數沒有本身的 this,它的 this 來自於外層做用域,也由於箭頭函數沒有本身的 this 這一特性,它不能被用做構造函數。 箭頭函數不使用上面 this 的四種標準規則,函數執行時,它會捕獲外層做用域的 this ,一經綁定再也沒法修改,而且高於 new 綁定。

說明

文章更多的內容是來自於《你不知道的JavaScript》上卷,固然加了本身的一些理解。沒時間去看書的同窗,不妨看看,查漏補缺。
React 的同窗看完,能夠再看下這篇 <理解:爲何React事件處理中要綁定this> ,鞏固下對 this 的理解。

相關文章
相關標籤/搜索