我希望找到關於「this」關鍵字的作用以及如何正確使用它的清晰解釋。
它的行為似乎很奇怪,我不完全明白為什麼。
this
如何運作以及何時應該使用它?
P粉0879514422023-10-13 14:03:41
與其他語言相比,this
關鍵字在 JavaScript 中的行為有所不同。在物件導向語言中,this
關鍵字指的是該類別的目前實例。在 JavaScript 中,this
的值由函數的呼叫上下文 (context.function()
#) 及其呼叫位置決定。 p>
<强>1。在全域上下文中使用時
當您在全域上下文中使用 this
時,它會綁定到全域物件(瀏覽器中的window
)
document.write(this); //[object Window]
當您在全域上下文中定義的函數內使用 this
時,this
仍然綁定到全域對象,因為該函數實際上是全域上下文的方法。 < /p>
function f1() { return this; } document.write(f1()); //[object Window]
上面的f1
是一個全域物件的方法。因此我們也可以在 window
物件上呼叫它,如下所示:
function f() { return this; } document.write(window.f()); //[object Window]
<强>2。當在物件方法內部使用時
當您在物件方法中使用 this
關鍵字時,this
會綁定到「直接」封閉物件。
var obj = { name: "obj", f: function () { return this + ":" + this.name; } }; document.write(obj.f()); //[object Object]:obj
上面我已將「立即」一詞放在雙引號中。這是為了表明,如果將物件嵌套在另一個物件中,則 this
會綁定到直接父物件。
var obj = { name: "obj1", nestedobj: { name:"nestedobj", f: function () { return this + ":" + this.name; } } } document.write(obj.nestedobj.f()); //[object Object]:nestedobj
即使你將 function 作為方法明確添加到物件中,它仍然遵循上述規則,即 this
仍然指向直接父物件。
var obj1 = { name: "obj1", } function returnName() { return this + ":" + this.name; } obj1.f = returnName; //add method to object document.write(obj1.f()); //[object Object]:obj1
<强>3。呼叫無上下文函數時
當您在沒有任何上下文(即不在任何物件上)的情況下呼叫的函數內部使用this
時,它會綁定到全域物件(瀏覽器中的window
)(即使函數是在物件內部定義的)。
var context = "global"; var obj = { context: "object", method: function () { function f() { var context = "function"; return this + ":" +this.context; }; return f(); //invoked without context } }; document.write(obj.method()); //[object Window]:global
用函數嘗試一切
#我們也可以用函數來嘗試以上幾點。但還是有些差異。
this
向函數新增成員。來指定它們。 new
運算子來建立其實例。 下面我嘗試了上面使用 Object 和 this
所做的所有操作,但首先建立函數而不是直接編寫物件。
/********************************************************************* 1. When you add variable to the function using this keyword, it gets added to the function prototype, thus allowing all function instances to have their own copy of the variables added. *********************************************************************/ function functionDef() { this.name = "ObjDefinition"; this.getName = function(){ return this+":"+this.name; } } obj1 = new functionDef(); document.write(obj1.getName() + "
"); //[object Object]:ObjDefinition /********************************************************************* 2. Members explicitly added to the function protorype also behave as above: all function instances have their own copy of the variable added. *********************************************************************/ functionDef.prototype.version = 1; functionDef.prototype.getVersion = function(){ return "v"+this.version; //see how this.version refers to the //version variable added through //prototype } document.write(obj1.getVersion() + "
"); //v1 /********************************************************************* 3. Illustrating that the function variables added by both above ways have their own copies across function instances *********************************************************************/ functionDef.prototype.incrementVersion = function(){ this.version = this.version + 1; } var obj2 = new functionDef(); document.write(obj2.getVersion() + "
"); //v1 obj2.incrementVersion(); //incrementing version in obj2 //does not affect obj1 version document.write(obj2.getVersion() + "
"); //v2 document.write(obj1.getVersion() + "
"); //v1 /********************************************************************* 4. `this` keyword refers to the immediate parent object. If you nest the object through function prototype, then `this` inside object refers to the nested object not the function instance *********************************************************************/ functionDef.prototype.nestedObj = { name: 'nestedObj', getName1 : function(){ return this+":"+this.name; } }; document.write(obj2.nestedObj.getName1() + "
"); //[object Object]:nestedObj /********************************************************************* 5. If the method is on an object's prototype chain, `this` refers to the object the method was called on, as if the method was on the object. *********************************************************************/ var ProtoObj = { fun: function () { return this.a } }; var obj3 = Object.create(ProtoObj); //creating an object setting ProtoObj //as its prototype obj3.a = 999; //adding instance member to obj3 document.write(obj3.fun()+"
");//999 //calling obj3.fun() makes //ProtoObj.fun() to access obj3.a as //if fun() is defined on obj3
<强>4。在建構函數內部使用時。
當函數用作建構函數時(即使用 new
關鍵字呼叫時),函數體內的 this
指向正在建構的新物件。 < /p>
var myname = "global context"; function SimpleFun() { this.myname = "simple function"; } var obj1 = new SimpleFun(); //adds myname to obj1 //1. `new` causes `this` inside the SimpleFun() to point to the // object being constructed thus adding any member // created inside SimipleFun() using this.membername to the // object being constructed //2. And by default `new` makes function to return newly // constructed object if no explicit return value is specified document.write(obj1.myname); //simple function
<强>5。當原型鏈上定義的函數內部使用時
#如果該方法位於物件的原型鏈上,則該方法內的 this
引用該方法被呼叫的對象,就像該方法是在該對像上定義的一樣。
var ProtoObj = { fun: function () { return this.a; } }; //Object.create() creates object with ProtoObj as its //prototype and assigns it to obj3, thus making fun() //to be the method on its prototype chain var obj3 = Object.create(ProtoObj); obj3.a = 999; document.write(obj3.fun()); //999 //Notice that fun() is defined on obj3's prototype but //`this.a` inside fun() retrieves obj3.a
<强>6。內部call()、apply()和bind()函數
#Function.prototype
上定義。 this
值。它們還可以在呼叫原始函數時將任何參數傳遞給原始函數。 fun.apply(obj1 [, argsArray])
將obj1
設為this
內的值code>fun() 並呼叫fun()
,傳遞argsArray
的元素作為其參數。 fun.call(obj1 [, arg1 [, arg2 [,arg3 [, ...]]]])
- 設定obj1 為fun ()
內部this
的值,並呼叫fun()
傳遞arg1, arg2, arg3, ...< /code> 作為其參數。
fun.bind(obj1 [, arg1 [, arg2 [,arg3 [, ...]]]])
- 傳回對函數的參考fun
,其中fun 內的this
綁定到obj1
,並且fun
的參數綁定到指定的參數arg1、arg2,arg3, ....
。 apply
、call
和 bind
之間的差異一定已經很明顯了。 apply
允許將參數指定為類似數組的對象,即具有數字 length
屬性和相應的非負整數屬性的對象。而 call
允許直接指定函數的參數。 apply
和 call
都會立即在指定上下文中並使用指定參數呼叫函數。另一方面,bind 只是傳回綁定到指定 this 值和參數的函數。我們可以透過將返回函數指派給變數來捕獲對此返回函數的引用,然後我們可以隨時呼叫它。 function add(inc1, inc2) { return this.a + inc1 + inc2; } var o = { a : 4 }; document.write(add.call(o, 5, 6)+"
"); //15 //above add.call(o,5,6) sets `this` inside //add() to `o` and calls add() resulting: // this.a + inc1 + inc2 = // `o.a` i.e. 4 + 5 + 6 = 15 document.write(add.apply(o, [5, 6]) + "
"); //15 // `o.a` i.e. 4 + 5 + 6 = 15 var g = add.bind(o, 5, 6); //g: `o.a` i.e. 4 + 5 + 6 document.write(g()+"
"); //15 var h = add.bind(o, 5); //h: `o.a` i.e. 4 + 5 + ? document.write(h(6) + "
"); //15 // 4 + 5 + 6 = 15 document.write(h() + "
"); //NaN //no parameter is passed to h() //thus inc2 inside add() is `undefined` //4 + 5 + undefined = NaN
7.事件處理程序內的 this
this
來引用對應的元素。這種直接的函數分配可以使用 addeventListener
方法或透過 onclick
等傳統事件註冊方法來完成。
)內使用 this
時,它指的是元素。 this
會解析為全域物件 window
。 attachEvent
將函數附加到事件處理程序時,可以實現與上述相同的行為。它不是將函數分配給事件處理程序(從而創建元素的函數方法),而是在事件上調用函數(在全局上下文中有效地調用它)。 我建議在 JSFiddle.
#ssscccUsing `this` "directly" inside event handler or event property
Using `this` "indirectly" inside event handler or event property
IE only:
<强>8。 ES6 箭頭函數中的 this
在箭頭函數中,this
的行為類似於公共變數:它將從其詞法範圍繼承。定義箭頭函數的函數的 this
將是箭頭函數的 this
。
所以,這與以下行為相同:
(function(){}).bind(this)
請參閱以下程式碼:
const globalArrowFunction = () => {
return this;
};
console.log(globalArrowFunction()); //window
const contextObject = {
method1: () => {return this},
method2: function(){
return () => {return this};
}
};
console.log(contextObject.method1()); //window
const contextLessFunction = contextObject.method1;
console.log(contextLessFunction()); //window
console.log(contextObject.method2()()) //contextObject
const innerArrowFunction = contextObject.method2();
console.log(innerArrowFunction()); //contextObject
P粉1565327062023-10-13 11:53:32
此
是JavaScript 中的關鍵字是執行上下文的屬性。它的主要用途是在函數和構造函數中。
this
的規則非常簡單(如果您堅持最佳實踐)。
this
的技術描述ECMAScript 標準定義this
透過抽像操作(縮寫為AO)ResolveThisBinding:
全球環境記錄,模組環境記錄,以及 函數環境記錄每個都有自己的 GetThisBinding 方法。
GetThisEnvironment AO 尋找目前的執行執行上下文的LexicalEnvironment 並尋找最接近的上升環境記錄(透過迭代存取其[[OuterEnv]] 屬性),該記錄具有this< /em> 綁定(即HasThisBinding 傳回true)。此過程以三種環境記錄類型之一結束。
this
的值通常取決於程式碼是否處於 嚴格模式。
GetThisBinding 的傳回值反映目前執行上下文的 this
值,因此每當建立新的執行上下文時,this
都會解析為不同的值。噹噹前執行上下文被修改時,也會發生這種情況。以下小節列出了可能發生這種情況的五種情況。
您可以將程式碼範例放入 AST 資源管理器中,以遵循規範詳細資訊。
這是在頂層評估的腳本程式碼,例如直接在 内:
當在腳本的初始全域執行上下文中時,評估 this
會導致 GetThisBinding 採取以下步驟:
全域環境記錄的[[GlobalThisValue]] 屬性總是設定為主機定義的全域物件,可透過globalThis
( Web 上的window
,Node.js 上的global
;MDN 上的文件)。請按照 InitializeHostDefinedRealm 的步驟來了解 [[GlobalThisValue]] 屬性是如何產生的。
ECMAScript 2015 中引進了模組。
這適用於模組,例如直接位於 內部時,而不是簡單的
中。
當在模組的初始全域執行上下文中時,評估 this
會導致 GetThisBinding 採取以下步驟:
在模組中,this
的值在全域上下文中始終是未定義
。模組隱式處於嚴格模式。
有兩種 eval
呼叫:直接和間接。這種差異自 ECMAScript 第五版以來就存在。
eval
呼叫通常類似於eval(
...);
或(eval)(
...< code>); (或((eval))(
…);
等)。 1 這只是 direct(如果呼叫表達式符合狹窄模式)。 2eval
呼叫涉及以任何其他方式呼叫函數引用eval
。它可以是eval?.(
...)
, (
..., eval)(
... )< /code>, window.eval(
...)
, eval.call(
...,
...)給定const aliasEval1 = eval; 等。 window.aliasEval2 = eval;
,也可以是aliasEval1(
…)
、aliasEval2(
…#)
程式碼>.分別給出const originalEval = eval; window.eval = (x) => originalEval(x);
,呼叫eval(
…)
也是間接的。 請參閱 chuckj 對「(1, eval)('this') vs eval('this') in JavaScript?」的回答。 和Dmitry Soshnikov 的ECMA-262- 5 詳細資訊– 第2 章:嚴格模式 (已存檔),適用於您可能使用間接 eval()
呼叫的情況。
PerformEval 執行 eval
程式碼。它創建一個新的 聲明性環境記錄 作為其 LexicalEnvironment,這是 GetThisEnvironment 從中取得 this
值。
然後,如果this
出現在eval
程式碼中,則GetThisEnvironment 被呼叫並傳回其值。
所建立的宣告式環境記錄取決於eval< /code> 呼叫是直接或間接的:
這意味著:
this
值不會改變;它取自名為 eval
的詞法作用域。 this
值是全域物件 (globalThis
)。 新函數
怎麼樣? — new Function
與eval
類似,但它不會立即呼叫程式碼;它建立了一個函數。 this 綁定不適用於此處的任何地方,除非呼叫函數時,函數正常運作,如下一節所述。
在呼叫函數時輸入函數程式碼。
呼叫函數的語法有四種類別。
#實際的函數呼叫發生在 Call AO 處,使用 thisValue 呼叫 根據上下文決定;這個參數在一長串與呼叫相關的呼叫中傳遞。 Call 呼叫 [[Call]] 函數的內部槽。這會呼叫 PrepareForOrdinaryCall,其中新的 函數環境記錄已建立:
此外,函數環境記錄中還有[[ThisValue]]欄位:
NewFunctionEnvironment 呼叫也會設定函數環境的 [[ThisBindingStatus]] 屬性。
[[Call]] 也呼叫 OrdinaryCallBindThis,其中適當的 thisArgument 基於以下因素決定:
確定後,最終呼叫新建立函數環境記錄的 BindThisValue 方法實際設定將 [[ThisValue]] 欄位新增至 thisArgument。
最後,這個欄位就是函數環境記錄 GetThisBinding AO 從下列位置取得 this
的值:
同樣,this 值的精確確定取決於許多因素;這只是一般性概述。有了這個技術背景,我們來看看所有的具體例子。
當計算箭頭函數時,[[ThisMode]]內部槽函數物件的屬性在 OrdinaryFunctionCreate 中設定為「lexical」。 p>
在 OrdinaryCallBindThis 處,它採用一個函數F:
這僅僅意味著綁定this的演算法的其餘部分被跳過。箭頭函數不會綁定自己的 this 值。
那麼,箭頭函數中的 this
是什麼?回顧一下 ResolveThisBinding 和 GetThisEnvironment,HasThisBinding 方法明確傳回 假#。
因此,我們會迭代地尋找外部環境。這個過程將在具有 this 綁定的三個環境之一結束。
這只是意味著,在箭頭函數體中,this
來自箭頭函數的詞法範圍,或者換句話說(來自箭頭函數與函數聲明/表達式:它們是否等價/可交換?):
在普通函數中(function
、方法),this
由函數的呼叫方式決定。
這就是這些「語法變體」派上用場的地方。
考慮這個物件包含一個函數:
const refObj = { func: function(){ console.log(this); } };
或:
const refObj = { func(){ console.log(this); } };
在以下任何函數呼叫中,func
內的 this
值將為 refObj
。 1< /p>
refObj.func()
refObj["func"]()
#refObj?.func()
#refObj.func?.()
#refObj.func``
#如果被呼叫的函數在語法上是基底物件的屬性,則該基底物件將是呼叫的“引用”,在通常情況下,它將是 this
的值。上面連結的評估步驟對此進行了解釋;例如,在refObj.func()
(或refObj["func"]()
)中,# CallMemberExpression 是整個表達式refObj.func()
,它由MemberExpression refObj.func
和參數 ()
。
而且,refObj.func
和 refObj
扮演三個角色,分別:
refObj.func
作為值是可呼叫的函數物件;對應的引用用於確定this
綁定。
可選連結和標記模板範例的工作方式非常相似:基本上,引用是?.()
之前、``
之前或<代码>()代码>。
EvaluateCall 使用 IsPropertyReference 來決定它在語法上是否是物件的屬性。它試圖取得引用的[[Base]] 屬性(例如,當應用於refObj.func
時為refObj
;或foo.bar
code> 當應用於foo.bar.baz
時)。如果將其寫為屬性,則 GetThisValue 將取得此 [[Base]] 屬性並使用它作為 this 值。
注意:Getters / Setters 的工作方式與方法,關於 this
。簡單屬性不會影響執行上下文,例如這裡,this
在全域範圍內:
const o = { a: 1, b: this.a, // Is `globalThis.a`. [this.a]: 2 // Refers to `globalThis.a`. };
with< 的调用/代码>
沒有基底引用的呼叫通常是不作為屬性呼叫的函數。例如:
func(); // As opposed to `refObj.func();`.
當傳遞或指派方法,或使用逗號運算子時,也會發生這種情況。這就是參考記錄和數值之間的差異相關的地方。
注意函數j
:依照規範,您會注意到j
只能傳回函數物件(Value)本身,而無法傳回引用記錄。因此,基本引用 refObj
遺失。
const g = (f) => f(); // No base ref. const h = refObj.func; const j = () => refObj.func; g(refObj.func); h(); // No base ref. j()(); // No base ref. (0, refObj.func)(); // Another common pattern to remove the base ref.
EvaluateCall 呼叫 呼叫,此處的thisValue為未定義。這在OrdinaryCallBindThis 中有所不同(F:函數物件;thisArgument:傳遞給Call 的thisValue) :< /p>
注意:步驟 5 將 this
的實際值設定為嚴格模式下提供的 thisArgument - 在本例中為 undefined
。在「草率模式」下,未定義或 null thisArgument 會導致 this
成為全域 this 值。
如果IsPropertyReference傳回false,則EvaluateCall 採取下列步驟:
這是未定義的 thisValue 可能來自:refEnv。 WithBaseObject() 總是未定義,除外 sec-with-statement-runtime- semantics-evaluation" rel="noreferrer">with
語句。在這種情況下,thisValue 將是綁定物件。
還有 Symbol.unscopables
(MDN 上的文件)來控制 with
綁定行為。
總結一下,到目前為止:
function f1(){ console.log(this); } function f2(){ console.log(this); } function f3(){ console.log(this); } const o = { f1, f2, [Symbol.unscopables]: { f2: true } }; f1(); // Logs `globalThis`. with(o){ f1(); // Logs `o`. f2(); // `f2` is unscopable, so this logs `globalThis`. f3(); // `f3` is not on `o`, so this logs `globalThis`. }
和:
"use strict"; function f(){ console.log(this); } f(); // Logs `undefined`. // `with` statements are not allowed in strict-mode code.
請注意,在計算 this
時,普通函數的定義位置並不重要。
.call
, .apply
,.bind
, thisArg 和基底元OrdinaryCallBindThis 的步驟5 的另一個結果,與步驟6.2(在規範),即原始this 值在「草率」模式下僅被強制轉換為物件。
為了檢查這一點,讓我們引入 this 值的另一個來源:重寫 this# 綁定的三個方法:4
#Function.prototype.apply(thisArg, argArray)
#Function.prototype.
{呼叫
, 綁定
} (thisArg, ...args)
李>
.bind
建立一個綁定函數,其this 綁定已設定為 thisArg 並且無法再次變更。 .call
和.apply
立即呼叫函數,並設定this 綁定到thisArg。
.call
和 .apply
直接對應到 Call ,使用指定的thisArg。 .bind
使用 BoundFunctionCreate 建立綁定函數。它們有自己的[[Call ]] 方法,它尋找函數物件的 [[BoundThis]] 內部槽。
設定自訂this值的範例:
function f(){ console.log(this); } const myObj = {}, g = f.bind(myObj), h = (m) => m(); // All of these log `myObj`. g(); f.bind(myObj)(); f.call(myObj); h(g);
對於對象,這在嚴格和非嚴格模式下是相同的。
現在,嘗試提供一個原始值:
function f(){ console.log(this); } const myString = "s", g = f.bind(myString); g(); // Logs `String { "s" }`. f.call(myString); // Logs `String { "s" }`.
在非嚴格模式下,基元被強制為其物件包裝形式。它與呼叫 Object("s")
或 new String("s")
時獲得的物件類型相同。在嚴格模式下,您可以使用原語:
"use strict"; function f(){ console.log(this); } const myString = "s", g = f.bind(myString); g(); // Logs `"s"`. f.call(myString); // Logs `"s"`.
圖書館利用這些方法,例如jQuery 將 this
設定為此處選擇的 DOM 元素:
$("button").click(function(){ console.log(this); // Logs the clicked button. });
新
#使用new
運算子將函數作為建構函式呼叫時,EvaluateNew 呼叫Construct,它呼叫[[Construct]] 方法。如果函數是基本建構子(即不是class extends
...{
...}
),它會設定thisArgument到從建構函數的原型建立的新物件。建構函式中 this
上設定的屬性最終會出現在產生的實例物件上。 this
會隱式傳回,除非您明確傳回自己的非原始值。
class
是建立建構子的新方法,在 ECMAScript 2015 中引入。
function Old(a){ this.p = a; } const o = new Old(1); console.log(o); // Logs `Old { p: 1 }`. class New{ constructor(a){ this.p = a; } } const n = new New(1); console.log(n); // Logs `New { p: 1 }`.
類別定義隱式地處於嚴格模式中:
class A{ m1(){ return this; } m2(){ const m1 = this.m1; console.log(m1()); } } new A().m2(); // Logs `undefined`.
超級
H4>
new
行為的例外是 class extends
...{
...}
,如上所述。衍生類別在呼叫時不會立即設定其this 值;它們只有在透過一系列super
呼叫到達基底類別後才會這樣做(在沒有自己的構造函數
的情況下隱式發生)。不允許在呼叫 super
之前使用 this
。
呼叫 super
呼叫具有呼叫的詞法範圍(函數環境記錄)的 this 值的超級建構子。 GetThisValue 對於 super
呼叫有一個特殊的規則。它使用 BindThisValue 將 this
設定為該環境記錄。
class DerivedNew extends New{ constructor(a, a2){ // Using `this` before `super` results in a ReferenceError. super(a); this.p2 = a2; } } const n2 = new DerivedNew(1, 2); console.log(n2); // Logs `DerivedNew { p: 1, p2: 2 }`.
ECMAScript 2022 中引入了實例字段和靜態字段。
當評估 class
時,ClassDefinitionEvaluation 為執行,修改執行執行上下文。對於每個 ClassElement:
this
指的是類別本身,this
引用實例。 私有欄位(例如#x
)和方法被加入到私有環境中。
靜態區塊目前是TC39 第 3 階段提案。靜態區塊的工作方式與靜態欄位和方法相同:其中的 this
引用類別本身。
請注意,在方法和 getters/setters 中,this
的工作方式與普通函數屬性中一樣。
class Demo{ a = this; b(){ return this; } static c = this; static d(){ return this; } // Getters, setters, private modifiers are also possible. } const demo = new Demo; console.log(demo.a, demo.b()); // Both log `demo`. console.log(Demo.c, Demo.d()); // Both log `Demo`.
1:(o.f)()
等價於o.f()
; (f)()
相當於f()
。 這篇 2ality 文章 (已存檔)。特別請參閱如何評估ParenthesizedExpression#。
2:它必須是MemberExpression em>,不能是屬性,必須具有完全「eval」的[[ReferencedName]] ,且必須是%eval%內在物件。
3:每當規格說「讓 ref 成為評估X 的結果」時,X 是您需要找到其求值步驟的某個表達式。例如,評估 MemberExpression 或 CallExpression 是 這些演算法。其中一些會產生參考記錄。
4:還有其他幾種本機和主機方法允許提供this 值,特別是Array.prototype.map
、< code>Array .prototype.forEach 等接受thisArg 作為第二個參數。任何人都可以創建自己的方法來更改this
,例如(func, thisArg) => func.bind(thisArg)
、(func, thisArg) => func 。 call(thisArg)
等。一如既往,MDN 提供了很棒的服務文件。
對於每個程式碼片段,回答以下問題:「標記行處 this
的值是多少?為什麼?」.
要顯示答案,請點選灰色框。
if(true){ console.log(this); // What is `this` here? }
globalThis
。標記的行在初始全域執行上下文中進行計算。
const obj = {}; function myFun(){ return { // What is `this` here? "is obj": this === obj, "is globalThis": this === globalThis }; } obj.method = myFun; console.log(obj.method());