this
this 關鍵字指代一段程式碼(例如函式體)應該執行的上下文。通常,它用於物件方法中,其中 this 指代該方法所屬的物件,從而允許在不同物件上覆用相同的方法。
JavaScript 中 this 的值取決於函式的呼叫方式(執行時繫結),而不是其定義方式。當一個普通函式作為物件的方法被呼叫時(obj.method()),this 指向該物件。當它作為獨立函式被呼叫時(未附加到物件:func()),this 通常指向全域性物件(在非嚴格模式下)或 undefined(在嚴格模式下)。Function.prototype.bind() 方法可以建立一個 this 繫結不變的函式,而Function.prototype.apply() 和Function.prototype.call() 方法也可以為特定的呼叫設定 this 值。
箭頭函式在處理 this 方面有所不同:它們在定義時從父級作用域繼承 this。這種行為使得箭頭函式特別適用於回撥和上下文保留。然而,箭頭函式沒有自己的 this 繫結。因此,它們的 this 值不能透過 bind()、apply() 或 call() 方法設定,在物件方法中它也不會指向當前物件。
試一試
const test = {
prop: 42,
func() {
return this.prop;
},
};
console.log(test.func());
// Expected output: 42
語法
this
值
在非嚴格模式下,this 總是對一個物件的引用。在嚴格模式下,它可以是任何值。有關如何確定該值的更多資訊,請參閱下面的描述。
描述
this 的值取決於它出現在哪個上下文:函式、類或全域性。
函式上下文
在函式內部,this 的值取決於函式的呼叫方式。可以將 this 視為函式的隱藏引數——就像函式定義中宣告的引數一樣,當評估函式體時,this 是語言為你建立的一個繫結。
對於普通函式(非箭頭函式、繫結函式等),this 的值是訪問該函式的物件。換句話說,如果函式呼叫形式為 obj.f(),那麼 this 指向 obj。例如:
function getThis() {
return this;
}
const obj1 = { name: "obj1" };
const obj2 = { name: "obj2" };
obj1.getThis = getThis;
obj2.getThis = getThis;
console.log(obj1.getThis()); // { name: 'obj1', getThis: [Function: getThis] }
console.log(obj2.getThis()); // { name: 'obj2', getThis: [Function: getThis] }
請注意,函式是相同的,但根據其呼叫方式,this 的值是不同的。這類似於函式引數的工作方式。
this 的值不是擁有該函式作為自有屬性的物件,而是用於呼叫該函式的物件。你可以透過呼叫原型鏈中物件的某個方法來證明這一點。
const obj3 = {
__proto__: obj1,
name: "obj3",
};
console.log(obj3.getThis()); // { name: 'obj3' }
this 的值總是根據函式的呼叫方式而變化,即使函式是在建立時定義在物件上的。
const obj4 = {
name: "obj4",
getThis() {
return this;
},
};
const obj5 = { name: "obj5" };
obj5.getThis = obj4.getThis;
console.log(obj5.getThis()); // { name: 'obj5', getThis: [Function: getThis] }
如果訪問方法的物件是一個原始值,那麼 this 也會是一個原始值——但僅限於函式處於嚴格模式下。
function getThisStrict() {
"use strict"; // Enter strict mode
return this;
}
// Only for demonstration — you should not mutate built-in prototypes
Number.prototype.getThisStrict = getThisStrict;
console.log(typeof (1).getThisStrict()); // "number"
如果函式在未附加到任何物件的情況下呼叫,則 this 將是 undefined——但僅限於函式處於嚴格模式下。
console.log(typeof getThisStrict()); // "undefined"
在非嚴格模式下,一個名為 this 替換的特殊過程確保 this 的值始終是一個物件。這意味著:
- 如果函式呼叫時
this設定為undefined或null,則this會被替換為globalThis。 - 如果函式呼叫時
this設定為原始值,則this會被替換為該原始值的包裝物件。
function getThis() {
return this;
}
// Only for demonstration — you should not mutate built-in prototypes
Number.prototype.getThis = getThis;
console.log(typeof (1).getThis()); // "object"
console.log(getThis() === globalThis); // true
在典型的函式呼叫中,this 會像引數一樣透過函式的字首(點號之前的部分)隱式傳遞。你還可以使用 Function.prototype.call()、Function.prototype.apply() 或 Reflect.apply() 方法顯式設定 this 的值。使用 Function.prototype.bind(),你可以建立一個新的函式,其 this 值是固定的,無論該函式如何呼叫都不會改變。當使用這些方法時,如果函式是非嚴格的,上述 this 替換規則仍然適用。
回撥函式
當函式作為回撥函式傳遞時,this 的值取決於回撥函式的呼叫方式,這由 API 的實現者決定。回撥函式通常以 this 值為 undefined 呼叫(直接呼叫,不附加到任何物件),這意味著如果函式是非嚴格的,則 this 的值為全域性物件(globalThis)。迭代陣列方法、Promise() 建構函式等都屬於這種情況。
function logThis() {
"use strict";
console.log(this);
}
[1, 2, 3].forEach(logThis); // undefined, undefined, undefined
一些 API 允許你為回撥函式的呼叫設定 this 值。例如,所有迭代陣列方法和相關方法,如 Set.prototype.forEach(),都接受一個可選的 thisArg 引數。
[1, 2, 3].forEach(logThis, { name: "obj" });
// { name: 'obj' }, { name: 'obj' }, { name: 'obj' }
偶爾,回撥函式會以 undefined 以外的 this 值呼叫。例如,JSON.parse() 的 reviver 引數和 JSON.stringify() 的 replacer 引數都被呼叫,其 this 設定為被解析/序列化的屬性所屬的物件。
箭頭函式
在箭頭函式中,this 會保留其封閉詞法上下文的 this 值。換句話說,在評估箭頭函式體時,語言不會建立新的 this 繫結。
例如,在全域性程式碼中,this 總是 globalThis,無論是否嚴格,因為存在全域性上下文繫結
const globalObject = this;
const foo = () => this;
console.log(foo() === globalObject); // true
箭頭函式在其周圍作用域的 this 值上建立了一個閉包,這意味著箭頭函式的行為就像它們是“自動繫結”的一樣——無論如何呼叫,this 都繫結到函式建立時的值(在上面的示例中是全域性物件)。這同樣適用於在其他函式內部建立的箭頭函式:它們的 this 仍然是其封閉詞法上下文的 this。請參閱下面的示例。
此外,當使用 call()、bind() 或 apply() 呼叫箭頭函式時,thisArg 引數會被忽略。不過,你仍然可以使用這些方法傳遞其他引數。
const obj = { name: "obj" };
// Attempt to set this using call
console.log(foo.call(obj) === globalObject); // true
// Attempt to set this using bind
const boundFoo = foo.bind(obj);
console.log(boundFoo() === globalObject); // true
建構函式
當函式用作建構函式時(使用 new 關鍵字),它的 this 將繫結到正在構造的新物件,無論建構函式是哪個物件上訪問的。this 的值將成為 new 表示式的值,除非建構函式返回另一個非原始值。
function C() {
this.a = 37;
}
let o = new C();
console.log(o.a); // 37
function C2() {
this.a = 37;
return { a: 38 };
}
o = new C2();
console.log(o.a); // 38
在第二個例子(C2)中,由於在構造過程中返回了一個物件,因此 this 繫結的新物件被丟棄。(這本質上使語句 this.a = 37; 成為死程式碼。它並非完全是死的,因為它確實會執行,但可以消除而沒有外部影響。)
super
當函式以 super.method() 形式呼叫時,method 函式內部的 this 值與 super.method() 呼叫周圍的 this 值相同,並且通常不等於 super 所引用的物件。這是因為 super.method 不像上面那樣是物件成員訪問——它是一種具有不同繫結規則的特殊語法。有關示例,請參閱 super 參考。
類上下文
類可以分為兩種上下文:靜態和例項。建構函式、方法和例項欄位初始化器(公共或私有)屬於例項上下文。靜態方法、靜態欄位初始化器和靜態初始化塊屬於靜態上下文。this 值在每個上下文中都不同。
類建構函式總是透過 new 呼叫,因此它們的行為與函式建構函式相同:this 值是正在建立的新例項。類方法的行為類似於物件字面量中的方法——this 值是訪問該方法的物件。如果該方法未轉移到另一個物件,則 this 通常是該類的一個例項。
靜態方法不是 this 的屬性。它們是類本身的屬性。因此,它們通常在類上訪問,並且 this 是類(或子類)的值。靜態初始化塊也以 this 設定為當前類進行評估。
欄位初始化器也在類的上下文中進行評估。例項欄位以 this 設定為正在構造的例項進行評估。靜態欄位以 this 設定為當前類進行評估。這就是為什麼欄位初始化器中的箭頭函式對於例項欄位繫結到例項,對於靜態欄位繫結到類的原因。
class C {
instanceField = this;
static staticField = this;
}
const c = new C();
console.log(c.instanceField === c); // true
console.log(C.staticField === C); // true
派生類建構函式
與基類建構函式不同,派生建構函式沒有初始的 this 繫結。呼叫 super() 會在建構函式中建立 this 繫結,其效果與評估以下程式碼行相同,其中 Base 是基類:
this = new Base();
警告:在呼叫 super() 之前引用 this 將丟擲錯誤。
派生類在呼叫 super() 之前不得返回,除非建構函式返回一個物件(因此 this 值被覆蓋),或者類根本沒有建構函式。
class Base {}
class Good extends Base {}
class AlsoGood extends Base {
constructor() {
return { a: 5 };
}
}
class Bad extends Base {
constructor() {}
}
new Good();
new AlsoGood();
new Bad(); // ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
全域性上下文
在全域性執行上下文(任何函式或類之外;可能在全域性作用域中定義的塊或箭頭函式內部)中,this 的值取決於指令碼執行的執行上下文。與回撥函式一樣,this 值由執行時環境(呼叫者)決定。
在指令碼的頂層,無論是否處於嚴格模式,this 都指向 globalThis。這通常與全域性物件相同——例如,如果原始碼放在 HTML <script> 元素中並作為指令碼執行,則 this === window。
注意:globalThis 通常與全域性物件是相同的概念(即,向 globalThis 新增屬性會使它們成為全域性變數)——瀏覽器和 Node 都是如此——但宿主可以為 globalThis 提供一個與全域性物件無關的不同值。
// In web browsers, the window object is also the global object:
console.log(this === window); // true
this.b = "MDN";
console.log(window.b); // "MDN"
console.log(b); // "MDN"
如果原始檔作為模組載入(對於 HTML,這意味著向 <script> 標籤新增 type="module"),則 this 在頂層始終是 undefined。
如果原始碼透過 eval() 執行,則對於直接 eval,this 與封閉上下文相同;對於間接 eval,則為 globalThis(就像它在單獨的全域性指令碼中執行一樣)。
function test() {
// Direct eval
console.log(eval("this") === this);
// Indirect eval, non-strict
console.log(eval?.("this") === globalThis);
// Indirect eval, strict
console.log(eval?.("'use strict'; this") === globalThis);
}
test.call({ name: "obj" }); // Logs 3 "true"
請注意,有些原始碼雖然看起來像全域性作用域,但在執行時實際上被包裝在一個函式中。例如,Node.js CommonJS 模組被包裝在一個函式中,並以 this 值設定為 module.exports 的方式執行。事件處理程式屬性的執行方式是 this 設定為它們所附加的元素。
物件字面量不建立 this 作用域——只有物件內部定義的函式(方法)才建立。在物件字面量中使用 this 會繼承來自周圍作用域的值。
const obj = {
a: this,
};
console.log(obj.a === window); // true
示例
函式上下文中的 this
this 引數的值取決於函式的呼叫方式,而不是其定義方式。
// An object can be passed as the first argument to 'call'
// or 'apply' and 'this' will be bound to it.
const obj = { a: "Custom" };
// Variables declared with var become properties of 'globalThis'.
var a = "Global";
function whatsThis() {
return this.a; // 'this' depends on how the function is called
}
whatsThis(); // 'Global'; the 'this' parameter defaults to 'globalThis' in non–strict mode
obj.whatsThis = whatsThis;
obj.whatsThis(); // 'Custom'; the 'this' parameter is bound to obj
使用 call() 和 apply(),你可以像傳遞顯式引數一樣傳遞 this 的值。
function add(c, d) {
return this.a + this.b + c + d;
}
const o = { a: 1, b: 3 };
// The first argument is bound to the implicit 'this' parameter; the remaining
// arguments are bound to the named parameters.
add.call(o, 5, 7); // 16
// The first argument is bound to the implicit 'this' parameter; the second
// argument is an array whose members are bound to the named parameters.
add.apply(o, [10, 20]); // 34
this 和物件轉換
在非嚴格模式下,如果函式呼叫時 this 值不是物件,則 this 值會替換為物件。null 和 undefined 會變成 globalThis。像 7 或 'foo' 這樣的原始值會使用相關的建構函式轉換為物件,因此原始數字 7 會轉換為 Number 包裝類,字串 'foo' 會轉換為 String 包裝類。
function bar() {
console.log(Object.prototype.toString.call(this));
}
bar.call(7); // [object Number]
bar.call("foo"); // [object String]
bar.call(undefined); // [object Window]
bind() 方法
呼叫 f.bind(someObject) 會建立一個與 f 具有相同函式體和作用域的新函式,但 this 的值會永久繫結到 bind 的第一個引數,無論函式如何被呼叫。
function f() {
return this.a;
}
const g = f.bind({ a: "azerty" });
console.log(g()); // azerty
const h = g.bind({ a: "yoo" }); // bind only works once!
console.log(h()); // azerty
const o = { a: 37, f, g, h };
console.log(o.a, o.f(), o.g(), o.h()); // 37 37 azerty azerty
箭頭函式中的 this
箭頭函式會建立對其封閉執行上下文的 this 值的閉包。在下面的示例中,我們建立了一個 obj,帶有一個方法 getThisGetter,該方法返回一個函式,該函式返回 this 的值。返回的函式作為箭頭函式建立,因此其 this 會永久繫結到其封閉函式的 this。getThisGetter 內部的 this 值可以在呼叫中設定,這反過來又設定了返回函式的返回值。我們假設 getThisGetter 是一個非嚴格函式,這意味著它包含在一個非嚴格指令碼中,並且沒有進一步巢狀在類或嚴格函式中。
const obj = {
getThisGetter() {
const getter = () => this;
return getter;
},
};
我們可以將 getThisGetter 作為 obj 的方法呼叫,這會將其內部的 this 繫結到 obj。返回的函式被賦值給變數 fn。現在,當呼叫 fn 時,返回的 this 值仍然是由對 getThisGetter 的呼叫設定的值,即 obj。如果返回的函式不是箭頭函式,則此類呼叫會導致 this 值為 globalThis,因為 getThisGetter 是非嚴格的。
const fn = obj.getThisGetter();
console.log(fn() === obj); // true
但如果你在不呼叫 obj 的方法的情況下解綁它,請務必小心,因為 getThisGetter 仍然是一個具有可變 this 值的方法。在以下示例中呼叫 fn2()() 返回 globalThis,因為它遵循 fn2() 中的 this,該 this 是 globalThis,因為它在未附加到任何物件的情況下被呼叫。
const fn2 = obj.getThisGetter;
console.log(fn2()() === globalThis); // true in non-strict mode
這種行為在定義回撥函式時非常有用。通常,每個函式表示式都會建立自己的 this 繫結,這會遮蔽上層作用域的 this 值。現在,如果你不關心 this 值,可以將函式定義為箭頭函式,並且只在你關心的地方建立 this 繫結(例如,在類方法中)。請參閱帶有 setTimeout() 的示例。
帶 getter 或 setter 的 this
getter 和 setter 中的 this 基於訪問屬性的物件,而不是定義屬性的物件。用作 getter 或 setter 的函式,其 this 繫結到正在設定或獲取屬性的物件。
function sum() {
return this.a + this.b + this.c;
}
const o = {
a: 1,
b: 2,
c: 3,
get average() {
return (this.a + this.b + this.c) / 3;
},
};
Object.defineProperty(o, "sum", {
get: sum,
enumerable: true,
configurable: true,
});
console.log(o.average, o.sum); // 2 6
DOM 事件處理程式中的 this
當函式用作事件處理程式時,其 this 引數繫結到放置監聽器的 DOM 元素上(某些瀏覽器對於使用 addEventListener() 以外的方法動態新增的監聽器不遵循此約定)。
// When called as a listener, turns the related element blue
function bluify(e) {
// Always true
console.log(this === e.currentTarget);
// true when currentTarget and target are the same object
console.log(this === e.target);
this.style.backgroundColor = "#A5D9F3";
}
// Get a list of every element in the document
const elements = document.getElementsByTagName("*");
// Add bluify as a click listener so when the
// element is clicked on, it turns blue
for (const element of elements) {
element.addEventListener("click", bluify);
}
內聯事件處理程式中的 this
當代碼從內聯事件處理程式屬性呼叫時,其 this 繫結到放置監聽器的 DOM 元素上:
<button onclick="alert(this.tagName.toLowerCase());">Show this</button>
上述 alert 顯示 button。然而,請注意,只有外部作用域的 this 以這種方式繫結:
<button onclick="alert((function () { return this; })());">
Show inner this
</button>
在這種情況下,內部函式的 this 引數繫結到 globalThis(即,在非嚴格模式下,如果呼叫中未傳入 this,則為預設物件)。
類中的繫結方法
與普通函式一樣,方法中 this 的值取決於它們的呼叫方式。有時,覆蓋此行為以使類中 this 始終引用類例項會很有用。為此,請在建構函式中繫結類方法:
class Car {
constructor() {
// Bind sayBye but not sayHi to show the difference
this.sayBye = this.sayBye.bind(this);
}
sayHi() {
console.log(`Hello from ${this.name}`);
}
sayBye() {
console.log(`Bye from ${this.name}`);
}
get name() {
return "Ferrari";
}
}
class Bird {
get name() {
return "Tweety";
}
}
const car = new Car();
const bird = new Bird();
// The value of 'this' in methods depends on their caller
car.sayHi(); // Hello from Ferrari
bird.sayHi = car.sayHi;
bird.sayHi(); // Hello from Tweety
// For bound methods, 'this' doesn't depend on the caller
bird.sayBye = car.sayBye;
bird.sayBye(); // Bye from Ferrari
注意:類總是處於嚴格模式。如果方法嘗試訪問 this 上的屬性,則使用未定義的 this 呼叫方法將丟擲錯誤。
const carSayHi = car.sayHi;
carSayHi(); // TypeError because the 'sayHi' method tries to access 'this.name', but 'this' is undefined in strict mode.
然而,請注意,自動繫結方法與將箭頭函式用於類屬性存在相同的問題:類的每個例項都將擁有自己方法副本,這會增加記憶體使用。僅在絕對必要時才使用它。你還可以模仿 Intl.NumberFormat.prototype.format() 的實現:將屬性定義為一個 getter,在訪問時返回一個繫結函式並將其儲存,這樣函式只建立一次,並且只在必要時建立。
with 語句中的 this
儘管 with 語句已棄用且在嚴格模式下不可用,但它們仍然是常規 this 繫結規則的例外。如果在 with 語句中呼叫函式,並且該函式是作用域物件的屬性,則 this 值繫結到作用域物件,就像存在 obj. 字首一樣。
const obj = {
foo() {
return this;
},
};
with (obj) {
console.log(foo() === obj); // true
}
規範
| 規範 |
|---|
| ECMAScript® 2026 語言規範 # sec-this-keyword |
瀏覽器相容性
載入中…