你真的懂this嗎?聊聊默認綁定,隱式綁定,顯示綁定,new綁定

https://github.com/YvetteLau/Blog/issues/6

this關鍵字是JavaScript中最複雜的機制之一,是一個特別的關鍵字,被自動定義在所有函數的作用域中,但是相信很多JavaScript開發者並不是非常清楚它究竟指向的是什麼。

再看一道題,控制檯打印出來的值是什麼?【瀏覽器運行環境】

var number = 5;
var obj = {
number: 3,
fn1: (function () {
var number;
this.number *= 2;
number = number * 2;
number = 3;
return function () {
var num = this.number;
this.number *= 2;
console.log(num);
number *= 3;
console.log(number);
}
})()
}
var fn1 = obj.fn1;
fn1.call(null);
obj.fn1();
console.log(window.number);
你真的懂this嗎?聊聊默認綁定,隱式綁定,顯示綁定,new綁定


為什麼要學習this?

首先,我們為什麼要學習this?

  1. this使用頻率很高,如果我們不懂this,那麼在看別人的代碼或者是源碼的時候,就會很吃力。
  2. 工作中,濫用this,卻沒明白this指向的是什麼,而導致出現問題,但是自己卻不知道哪裡出問題了。
  3. 合理的使用this,可以讓我們寫出簡潔且複用性高的代碼。
  4. 面試的高頻問題,回答不好,抱歉,請回去等通知。

this是什麼?首先記住this不是指向自身!this 就是一個指針,指向調用函數的對象。這句話我們都知道,但是很多時候,我們未必能夠準確判斷出this究竟指向的是什麼?為了能夠一眼看出this指向的是什麼,我們首先需要知道this的綁定規則有哪些?

  1. 默認綁定
  2. 隱式綁定
  3. 硬綁定
  4. new綁定

默認綁定

默認綁定,在不能應用其它綁定規則時使用的默認規則,通常是獨立函數調用。

function sayHi(){
console.log('Hello,', this.name);
}
var name = 'YvetteLau';
sayHi();
你真的懂this嗎?聊聊默認綁定,隱式綁定,顯示綁定,new綁定

在調用sayHi()時,應用了默認綁定,this指向全局對象(非嚴格模式下),嚴格模式下,this指向undefined,undefined上沒有this對象,會拋出錯誤。

上面的代碼,如果在瀏覽器環境中運行,那麼結果就是 Hello,YvetteLau.但是如果在node環境中運行,結果就是 Hello,undefined.這是因為node中name並不是掛在全局對象上的。如不特殊說明,默認為瀏覽器環境執行結果。

隱式綁定

函數的調用是在某個對象上觸發的,即調用位置上存在上下文對象。典型的形式為 XXX.fun().我們來看一段代碼:

function sayHi(){
console.log('Hello,', this.name);
}
var person = {
name: 'YvetteLau',
sayHi: sayHi
}
var name = 'Wiliam';
person.sayHi();
你真的懂this嗎?聊聊默認綁定,隱式綁定,顯示綁定,new綁定

打印的結果是 Hello,YvetteLau.

sayHi函數聲明在外部,嚴格來說並不屬於person,但是在調用sayHi時,調用位置會使用person的上下文來引用函數,隱式綁定會把函數調用中的this(即此例sayHi函數中的this)綁定到這個上下文對象(即此例中的person)

需要注意的是:對象屬性鏈中只有

最後一層會影響到調用位置。

function sayHi(){
console.log('Hello,', this.name);
}
var person2 = {
name: 'Christina',
sayHi: sayHi
}
var person1 = {
name: 'YvetteLau',
friend: person2
}
person1.friend.sayHi();
你真的懂this嗎?聊聊默認綁定,隱式綁定,顯示綁定,new綁定

結果是:

Hello, Christina.

因為只有最後一層會確定this指向的是什麼,不管有多少層,在判斷this的時候,我們只關注最後一層,即此處的friend。

隱式綁定有一個大陷阱,綁定很容易丟失或者說容易給我們造成誤導.

function sayHi(){
console.log('Hello,', this.name);
}
var person = {
name: 'YvetteLau',
sayHi: sayHi
}
var name = 'Wiliam';
var Hi = person.sayHi;
Hi();
你真的懂this嗎?聊聊默認綁定,隱式綁定,顯示綁定,new綁定

結果是: Hello,Wiliam.

這是為什麼呢,Hi直接指向了sayHi的引用,在調用時,跟person沒有關係,針對此類問題,建議大家只需牢牢繼續這個格式:XXX.fn();fn()前如果什麼都沒有,那麼肯定不是隱式綁定,但是也不一定就是默認綁定,這裡有點小疑問,後面會說到。

除了上面這種丟失之外,隱式綁定的丟失是發生在回調函數中(事件回調也是其中一種),我們來看下面一個例子:

你真的懂this嗎?聊聊默認綁定,隱式綁定,顯示綁定,new綁定

結果為:

Hello, Wiliam
Hello, Wiliam
Hello, Christina

第一條輸出很容易理解,setTimeout的回調函數中,this使用的是默認綁定,非嚴格模式下,執行的是全局對象。

第二條輸出是不是有點迷惑了?說好XXX.fun()的時候,fun中的this指向的是XXX呢,為什麼這次卻不是這樣了?

其實這裡我們可以這樣理解: setTimeout(fn,delay){ fn(); },相當於是將person2.sayHi賦值給了一個變量,最後執行了變量,這個時候,sayHi中的this顯然和person2就沒有關係了。

第三條雖然也是在setTimeout的回調中,但是我們可以看出,這是執行的是person2.sayHi()使用的是隱式綁定,因此這是this指向的是person2,跟當前的作用域沒有任何關係。

顯式綁定

顯式綁定比較好理解,就是通過call,apply,bind的方式,顯式的指定this所指向的對象。

call,apply和bind的第一個參數,就是對應函數的this所指向的對象。call和apply的作用一樣,只是傳參方式不同。call和apply都會執行對應的函數,而bind方法不會。

你真的懂this嗎?聊聊默認綁定,隱式綁定,顯示綁定,new綁定

輸出的結果為: Hello, YvetteLau. 因為使用硬綁定明確將this綁定在了person上。

那麼,使用了硬綁定,是不是意味著不會出現隱式綁定所遇到的綁定丟失呢?顯然不是這樣的,繼續往下看。

你真的懂this嗎?聊聊默認綁定,隱式綁定,顯示綁定,new綁定

輸出的結果是 Hello, Wiliam. 原因很簡單,Hi.call(person, person.sayHi)的確是將this綁定到Hi中的this了。但是在執行fn的時候,相當於直接調用了sayHi方法(記住: person.sayHi已經被賦值給fn了,隱式綁定也丟了),沒有指定this的值,對應的是默認綁定

現在,我們希望綁定不會丟失,要怎麼做?很簡單,調用fn的時候,也給它硬綁定

你真的懂this嗎?聊聊默認綁定,隱式綁定,顯示綁定,new綁定

此時,輸出的結果為: Hello, YvetteLau,因為person被綁定到Hi函數中的this上,fn又將這個對象綁定給了sayHi的函數。這時,sayHi中的this指向的就是person對象。

new 綁定

javaScript和C++不一樣,並沒有類,在javaScript中,構造函數只是使用new操作符時被調用的函數,這些函數和普通的函數並沒有什麼不同,它不屬於某個類,也不可能實例化出一個類。任何一個函數都可以使用new來調用,因此其實並不存在構造函數,而只有對於函數的“構造調用”。

使用new來調用函數,會自動執行下面的操作:

  1. 創建一個新對象
  2. 將構造函數的作用域賦值給新對象,即this指向這個新對象
  3. 執行構造函數中的代碼
  4. 返回新對象

因此,我們使用new來調用函數的時候,就會新對象綁定到這個函數的this上。

你真的懂this嗎?聊聊默認綁定,隱式綁定,顯示綁定,new綁定

輸出結果為 Hello, Yevtte, 原因是因為在var Hi = new sayHi('Yevtte');這一步,會將sayHi中的this綁定到Hi對象上。

綁定優先級

我們知道了this有四種綁定規則,但是如果同時應用了多種規則,怎麼辦?

顯然,我們需要了解哪一種綁定方式的優先級更高,這四種綁定的優先級為:

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

這個規則時如何得到的,大家如果有興趣,可以自己寫個demo去測試,或者記住上面的結論即可。

綁定例外

如果我們將null或者是undefined作為this的綁定對象傳入call、apply或者是bind,這些值在調用時會被忽略

,實際應用的是默認綁定規則。

你真的懂this嗎?聊聊默認綁定,隱式綁定,顯示綁定,new綁定

輸出的結果是 Hello, Wiliam,因為這時實際應用的是默認綁定規則。

箭頭函數

箭頭函數是ES6中新增的,它和普通函數有一些區別,

箭頭函數沒有自己的this,它的this繼承於外層代碼庫中的this。箭頭函數在使用時,需要注意以下幾點:

(1)函數體內的this對象,繼承的是外層代碼塊的this。

(2)不可以當作構造函數,也就是說,不可以使用new命令,否則會拋出一個錯誤。

(3)不可以使用arguments對象,該對象在函數體內不存在。如果要用,可以用 rest 參數代替。

(4)不可以使用yield命令,因此箭頭函數不能用作 Generator 函數。

(5)箭頭函數沒有自己的this,所以不能用call()、apply()、bind()這些方法去改變this的指向.

看看箭頭函數的this是什麼?

你真的懂this嗎?聊聊默認綁定,隱式綁定,顯示綁定,new綁定

那麼這是為什麼呢?如果大家說箭頭函數中的this是定義時所在的對象,這樣的結果顯示不是大家預期的,按照這個定義,say中的this應該是obj才對。

我們來分析一下上面的執行結果:

obj.hi(); 對應了this的隱式綁定規則,this綁定在obj上,所以輸出obj,很好理解。

hi(); 這一步執行的就是箭頭函數,箭頭函數繼承上一個代碼庫的this,剛剛我們得出上一層的this是obj,顯然這裡的this就是obj.

執行sayHi();這一步也很好理解,我們前面說過這種隱式綁定丟失的情況,這個時候this執行的是默認綁定,this指向的是全局對象window.

fun1(); 這一步執行的是箭頭函數,如果按照之前的理解,this指向的是箭頭函數定義時所在的對象,那麼這兒顯然是說不通。OK,按照箭頭函數的this是繼承於外層代碼庫的this就很好理解了。外層代碼庫我們剛剛分析了,this指向的是window,因此這兒的輸出結果是window.

obj.say(); 執行的是箭頭函數,當前的代碼塊obj中是不存在this的,只能往上找,就找到了全局的this,指向的是window.

看看箭頭函數中的this真的是靜態的嗎?

你真的懂this嗎?聊聊默認綁定,隱式綁定,顯示綁定,new綁定

可以看出,fun1和fun2對應的是同樣的箭頭函數,但是this的輸出結果是不一樣的。

所以,請大家牢牢記住一點:

箭頭函數沒有自己的this,箭頭函數中的this繼承於外層代碼庫中的this.

總結

關於this的規則,至此,就告一段落了,但是想要一眼就能看出this所綁定的對象,還需要不斷的訓練。

我們來回顧一下,最初的問題。

1. 如何準確判斷this指向的是什麼?

  1. 函數是否在new中調用(new綁定),如果是,那麼this綁定的是新創建的對象。
  2. 函數是否通過call,apply調用,或者使用了bind(即硬綁定),如果是,那麼this綁定的就是指定的對象。
  3. 函數是否在某個上下文對象中調用(隱式綁定),如果是的話,this綁定的是那個上下文對象。一般是obj.foo()
  4. 如果以上都不是,那麼使用默認綁定。如果在嚴格模式下,則綁定到undefined,否則綁定到全局對象。
  5. 如果把Null或者undefined作為this的綁定對象傳入call、apply或者bind,這些值在調用時會被忽略,實際應用的是默認綁定規則。
  6. 如果是箭頭函數,箭頭函數的this繼承的是外層代碼塊的this。

2. 執行過程解析

你真的懂this嗎?聊聊默認綁定,隱式綁定,顯示綁定,new綁定

我們來分析一下,這段代碼的執行過程。

1.在定義obj的時候,fn對應的閉包就執行了,返回其中的函數,執行閉包中代碼時,顯然應用不了new綁定(沒有出現new 關鍵字),硬綁定也沒有(沒有出現call,apply,bind關鍵字),隱式綁定有沒有?很顯然沒有,如果沒有XX.fn(),那麼可以肯定沒有應用隱式綁定,所以這裡應用的就是默認綁定了,非嚴格模式下this綁定到了window上(瀏覽器執行環境)。【這裡很容易被迷惑的就是以為this指向的是obj,一定要注意,除非是箭頭函數,否則this跟詞法作用域是兩回事,一定要牢記在心】

你真的懂this嗎?聊聊默認綁定,隱式綁定,顯示綁定,new綁定

2.myFun.call(null);我們前面說了,call的第一個參數傳null,調用的是默認綁定;

你真的懂this嗎?聊聊默認綁定,隱式綁定,顯示綁定,new綁定

執行時:

你真的懂this嗎?聊聊默認綁定,隱式綁定,顯示綁定,new綁定

3.obj.fn();應用了隱式綁定,fn中的this對應的是obj.

你真的懂this嗎?聊聊默認綁定,隱式綁定,顯示綁定,new綁定

4.最後一步console.log(window.number);輸出的結果是20

因此組中結果為:

10
9
3
27
20
歡迎關注,分享更多


分享到:


相關文章: