深入理解函數(shù)執(zhí)行上下文 this
JavaScript 中的 this 是什么
關于 this,我們得先從執(zhí)行上下文說起。我們知道:執(zhí)行上下文中包含了變量環(huán)境、詞法環(huán)境、外部環(huán)境,當然也包括 this,具體你可以參考下圖:

從圖中可以看出,this 是和執(zhí)行上下文綁定的,也就是說每個執(zhí)行上下文中都有一個 this。執(zhí)行上下文主要分為三種——全局執(zhí)行上下文、函數(shù)執(zhí)行上下文和 eval 執(zhí)行上下文,所以對應的 this 也只有這三種——全局執(zhí)行上下文中的 this、函數(shù)中的 this 和 eval 中的 this。
不過由于 eval 我們使用的不多,所以本文我們對此就不做介紹了,如果你感興趣的話,可以自行搜索和學習相關知識。
那么接下來我們就重點講解下全局執(zhí)行上下文中的 this和函數(shù)執(zhí)行上下文中的 this。
全局執(zhí)行上下文中的 this
首先我們來看看全局執(zhí)行上下文中的 this 是什么。
你可以在控制臺中輸入console.log(this)來打印出來全局執(zhí)行上下文中的 this,最終輸出的是 window 對象。所以你可以得出這樣一個結論:全局執(zhí)行上下文中的 this 是指向 window 對象的。這也是 this 和作用域鏈的唯一交點,作用域鏈的最底端包含了 window 對象,全局執(zhí)行上下文中的 this 也是指向 window 對象。
函數(shù)執(zhí)行上下文中的 this
現(xiàn)在你已經知道全局對象中的 this 是指向 window 對象了,那么接下來,我們就來重點分析函數(shù)執(zhí)行上下文中的 this。還是先看下面這段代碼:
function foo() {
console.log(this);
}
foo();
我們在 foo 函數(shù)內部打印出來 this 值,執(zhí)行這段代碼,打印出來的也是 window 對象,這說明在默認情況下調用一個函數(shù),其執(zhí)行上下文中的 this 也是指向 window 對象的。估計你會好奇,那能不能設置執(zhí)行上下文中的 this 來指向其他對象呢?答案是肯定的。通常情況下,有下面三種方式來設置函數(shù)執(zhí)行上下文中的 this 值。
1. 通過函數(shù)的 call 方法設置
你可以通過函數(shù)的call方法來設置函數(shù)執(zhí)行上下文的 this 指向,比如下面這段代碼,我們就并沒有直接調用 foo 函數(shù),而是調用了 foo 的 call 方法,并將 bar 對象作為 call 方法的參數(shù)。
let bar = {
myName: " name1 ",
test1: 1,
};
function foo() {
this.myName = " name2 ";
}
foo.call(bar);
console.log(bar);
console.log(myName);
執(zhí)行這段代碼,然后觀察輸出結果,你就能發(fā)現(xiàn) foo 函數(shù)內部的 this 已經指向了 bar 對象,因為通過打印 bar 對象,可以看出 bar 的 myName 屬性已經由“name1”變?yōu)?ldquo;name2”了,同時在全局執(zhí)行上下文中打印 myName,JavaScript 引擎提示該變量未定義。
其實除了 call 方法,你還可以使用bind和apply方法來設置函數(shù)執(zhí)行上下文中的 this,僅僅是語法稍有不同。
2. 通過對象調用方法設置
要改變函數(shù)執(zhí)行上下文中的 this 指向,除了通過函數(shù)的 call 方法來實現(xiàn)外,還可以通過對象調用的方式,比如下面這段代碼:
var myObj = {
name: " name ",
showThis: function () {
console.log(this);
},
};
myObj.showThis();
在這段代碼中,我們定義了一個 myObj 對象,該對象是由一個 name 屬性和一個 showThis 方法組成的,然后再通過 myObj 對象來調用 showThis 方法。執(zhí)行這段代碼,你可以看到,最終輸出的 this 值是指向 myObj 的。
所以,你可以得出這樣的結論:使用對象來調用其內部的一個方法,該方法的 this 是指向對象本身的。
其實,你也可以認為 JavaScript 引擎在執(zhí)行myObject.showThis()時,將其轉化為了:
myObj.showThis.call(myObj)
接下來我們稍微改變下調用方式,把 showThis 賦給一個全局對象,然后再調用該對象,代碼如下所示:
var myObj = {
name: " time ",
showThis: function () {
this.name = " bang ";
console.log(this);
},
};
var foo = myObj.showThis;
foo();
執(zhí)行這段代碼,你會發(fā)現(xiàn) this 又指向了全局 window 對象。
所以通過以上兩個例子的對比,你可以得出下面這樣兩個結論:
- 在全局環(huán)境中調用一個函數(shù),函數(shù)內部的 this 指向的是全局變量 window。
- 通過一個對象來調用其內部的一個方法,該方法的執(zhí)行上下文中的 this 指向對象本身。
3. 通過構造函數(shù)中設置
你可以像這樣設置構造函數(shù)中的 this,如下面的示例代碼:
function CreateObj() {
this.name = " time ";
}
var myObj = new CreateObj();
在這段代碼中,我們使用 new 創(chuàng)建了對象 myObj,那你知道此時的構造函數(shù) CreateObj 中的 this 到底指向了誰嗎?
其實,當執(zhí)行 new CreateObj() 的時候,JavaScript 引擎做了如下四件事:
- 首先創(chuàng)建了一個空對象 tempObj;
- 接著調用 CreateObj.call 方法,并將 tempObj 作為 call 方法的參數(shù),這樣當 CreateObj 的執(zhí)行上下文創(chuàng)建時,它的 this 就指向了 tempObj 對象;
- 然后執(zhí)行 CreateObj 函數(shù),此時的 CreateObj 函數(shù)執(zhí)行上下文中的 this 指向了 tempObj 對象;
- 最后返回 tempObj 對象。
這樣,我們就通過 new 關鍵字構建好了一個新對象,并且構造函數(shù)中的 this 其實就是新對象本身。
this 的設計缺陷以及應對方案
就我個人而言,this 并不是一個很好的設計,因為它的很多使用方法都沖擊人的直覺,在使用過程中存在著非常多的坑。下面咱們就來一起看看那些 this 設計缺陷。
1. 嵌套函數(shù)中的 this 不會從外層函數(shù)中繼承
我認為這是一個嚴重的設計錯誤,并影響了很多開發(fā)者。
至于如何解決?你可以在函數(shù)中聲明一個變量 self 用來保存 this。當然也可以使用 ES6 中的箭頭函數(shù)來解決這個問題。
2. 普通函數(shù)中的 this 默認指向全局對象 window
上面我們已經介紹過了,在默認情況下調用一個函數(shù),其執(zhí)行上下文中的 this 是默認指向全局對象 window 的。
不過這個設計也是一種缺陷,因為在實際工作中,我們并不希望函數(shù)執(zhí)行上下文中的 this 默認指向全局對象,因為這樣會打破數(shù)據(jù)的邊界,造成一些誤操作。如果要讓函數(shù)執(zhí)行上下文中的 this 指向某個對象,最好的方式是通過 call 方法來顯示調用。
這個問題可以通過設置 JavaScript 的“嚴格模式”來解決。在嚴格模式下,默認執(zhí)行一個函數(shù),其函數(shù)的執(zhí)行上下文中的 this 值是 undefined,這就解決上面的問題了。
總結
回顧下內容:
首先,在使用 this 時,為了避坑,你要謹記以下三點:
- 當函數(shù)作為對象的方法調用時,函數(shù)中的 this 就是該對象;
- 當函數(shù)被正常調用時,在嚴格模式下,this 值是 undefined,非嚴格模式下 this 指向的是全局對象 window;
- 嵌套函數(shù)中的 this 不會繼承外層函數(shù)的 this 值。
最后,我們還提了一下箭頭函數(shù),因為箭頭函數(shù)沒有自己的執(zhí)行上下文,所以箭頭函數(shù)的 this 就是它外層函數(shù)的 this。
以上就是深入理解函數(shù)執(zhí)行上下文 this的詳細內容,更多關于函數(shù)執(zhí)行上下文 this的資料請關注腳本之家其它相關文章!
相關文章
You-Dont-Know-JS詞法作用域及兩種常見的模型學習文檔
這篇文章主要為大家介紹了JS?詞法作用域及兩種常見的模型詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-08-08
pnpm?tauri?build?默認com.tauri.dev打包報錯解決
這篇文章主要介紹了pnpm?tauri?build?默認com.tauri.dev打包報錯解決方式,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-08-08
Blob實現(xiàn)與File?DataURL?canvas相互轉換示例
這篇文章主要為大家介紹了Blob實現(xiàn)與File?DataURL?canvas相互轉換的示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-06-06

