從 JavaScript 執行上下文的視角講清楚 this
theme: devui-blue highlight: a11y-dark
我正在參加「掘金·啟航計劃」
前言
在對象內部的方法中使用對象內部的屬性是一個非常普遍的需求。但是 JavaScript 的作用域機制並不支持這一點,基於這個需求,JavaScript 有另外一套 this 機制。
this 是和執行上下文綁定的,也就是説每個執行上下文中都有一個 this。執行上下文主要分為三種—— 全局執行上下文、函數執行上下文 和 eval 執行上下文,所以對應的 this 也只有這三種——全局執行上下文中的 this、函數中的 this 和 eval 中的 this。
全局執行上下文中的 this
全局執行上下文中的 this
是指向 window
對象的。這也是 this
和作用域鏈的 唯一交點,作用域鏈的最底端包含了 window
對象,全局執行上下文中的 this
也是指向 window
對象。
函數執行上下文中的 this
js
function foo() {
console.log(this);
}
foo();
執行這段代碼,打印出來的也是 window
對象,這説明在默認情況下調用一個函數,其執行上下文中的 this
也是指向 window
對象的。
通常情況下,有下面三種方式來設置函數執行上下文中的 this
值:
1. 通過函數的 call 方法設置
js
let bar = {
myName: "極客邦",
test1: 1,
};
function foo() {
this.myName = "極客時間";
}
foo.call(bar);
console.log(bar);
console.log(myName);
執行這段代碼,你就能發現 foo
函數內部的 this
已經指向了 bar
對象,因為通過打印 bar
對象,可以看出 bar
的 myName
屬性已經由“極客邦”變為“極客時間”了,同時在全局執行上下文中打印 myName
,JavaScript 引擎提示該變量未定義。其實除了 call
方法,你還可以使用 bind
和 apply
方法來設置函數執行上下文中的 this
。
2. 通過對象調用方法設置
js
var myObj = {
name: "極客時間",
showThis: function () {
console.log(this);
},
};
myObj.showThis();
執行這段代碼,你可以看到,最終輸出的 this
值是指向 myObj
的。所以,你可以得出這樣的結論:使用對象來調用其內部的一個方法,該方法的 this
是指向對象本身的。
接下來我們稍微改變下調用方式,把 showThis
賦給一個全局對象,然後再調用該對象,代碼如下所示:
js
var myObj = {
name: "極客時間",
showThis: function () {
this.name = "極客邦";
console.log(this);
},
};
var foo = myObj.showThis;
foo();
執行這段代碼,你會發現 this
又指向了全局 window
對象。
結論:
在全局環境中調用一個函數,函數內部的
this
指向的是全局變量window
。通過一個對象來調用其內部的一個方法,該方法的執行上下文中的
this
指向對象本身。
3. 通過構造函數中設置
js
function CreateObj() {
this.name = "極客時間";
}
var myObj = new CreateObj();
當執行 new CreateObj()
的時候,JavaScript 引擎做了如下四件事:
首先創建了一個空對象
tempObj
接着調用
CreateObj.call
方法,並將tempObj
作為call
方法的參數,這樣當CreateObj
的執行上下文創建時,它的this
就指向了tempObj
對象然後執行
CreateObj
函數,此時的CreateObj
函數執行上下文中的this
指向了tempObj
對象最後返回
tempObj
對象
js
var tempObj = {};
CreateObj.call(tempObj);
return tempObj;
這樣,就通過 new
關鍵字構建好了一個新對象,並且構造函數中的 this
其實就是新對象本身。
this 的設計缺陷以及應對方案
1. 嵌套函數中的 this 不會從外層函數中繼承
js
var myObj = {
name: "極客時間",
showThis: function () {
console.log(this);
function bar() {
console.log(this);
}
bar();
},
};
myObj.showThis();
執行這段代碼後,會發現函數 bar
中的 this
指向的是全局 window
對象,而函數 showThis
中的 this
指向的是 myObj
對象。
可以通過一個小技巧來解決這個問題,比如在 showThis
函數中聲明一個變量 that
用來保存 this
,然後在 bar
函數中使用 that
。其實,這個方法的的本質是把 this
體系轉換為了作用域的體系。
其實,你也可以使用 ES6 中的箭頭函數來解決這個問題:
js
var myObj = {
name: "極客時間",
showThis: function () {
console.log(this);
var bar = () => {
console.log(this);
};
bar();
},
};
myObj.showThis();
這是因為 ES6 中的箭頭函數並不會創建其自身的執行上下文,所以箭頭函數中的 this
取決於它的外部函數。
2. 普通函數中的 this 默認指向全局對象 window
在實際工作中,我們並不希望函數執行上下文中的 this
默認指向全局對象,因為這樣會打破數據的邊界,造成一些誤操作。如果要讓函數執行上下文中的 this
指向某個對象,最好的方式是通過 call
方法來顯示調用。
可以通過設置 JavaScript 的 嚴格模式 來解決(在第一行加上 "use strict";
)。在嚴格模式下,默認執行一個函數,其函數的執行上下文中的 this
值是 undefined
。