this 關鍵字
在 JavaScript 中,this 是一個非常特別且強大的關鍵字,但同時也常常讓人感到困惑。
this 的值取決於它被呼叫時的 執行上下文 (Execution Context) 的位置。
簡單來說,this 就是「呼叫我的人」。
是誰執行了這段程式碼,this 就指向誰。
this 的指向規則
this 的值主要可以根據以下幾種情況來判斷:
1. 在全域作用域中
在瀏覽器環境中
this指向window。
// 在瀏覽器中會輸出 Window 物件
console.log(this); // Window: { console, alert, confirm, navigator, ... }在 Node.js 環境中
this指向globalThis,預設是一個空物件{}
// 在 Node.js 中會輸出 global 物件
console.log(this); // {}同時根據 作用域#補充變數章節沒有提到的-var-let-const-差異, var 宣告的變數會被提升到全域作用域,所以當在全域作用域中使用 var 宣告變數時,這些變數同時也會成為全域 this 物件的屬性。
// Browser 環境
var globalVar = 'I am a global variable in browser';
// 在瀏覽器中,全域 this = window, 所以 window.globalVar = this.globalVar = 'I am a global variable in browser'
console.log(this.globalVar === window.globalVar); // true
// Node.js 環境
var globalVar = 'I am a global variable in Node.js';
// this: { globalVar: 'I am a global variable in Node.js' }
console.log(this.globalVar === globalThis.globalVar); // true2. 在一般函式中
在一般函式(非物件方法、非箭頭函式)中直接呼叫時,this 的值會有點棘手,它會因為是否開啟了嚴格模式而有所不同。
嚴格模式
嚴格模式是 JavaScript 的一種運行模式,可以通過在程式碼的開頭加上 'use strict'; 來啟用。
它會讓 JavaScript 的一些行為變得更嚴格,避免一些常見的錯誤。
用一個表格來說明會比較清晰:
| 環境 | 嚴格模式 ✅ | 嚴格模式 ❌ |
|---|---|---|
| 瀏覽器 | undefined | window |
| Node.js | undefined | globalThis |
// Browser 環境
function showThis() {
'use strict'; // 開啟嚴格模式
console.log(this); // undefined
}
function showThisNonStrict() {
console.log(this); // Window
}
// Node.js 環境
function showThisNode() {
'use strict'; // 開啟嚴格模式
console.log(this); // undefined
}
function showThisNodeNonStrict() {
console.log(this); // globalThis => {}
}建議
現代 JavaScript 開發通常預設或建議使用嚴格模式,以避免一些意料之外的行為。所以你可以傾向於記住:在一般函式中,this 是 undefined。
3. 作為物件的方法
這是最常見也最直觀的情況:當函式作為一個物件的方法被呼叫時,this 會指向該物件。
const person = {
name: 'Aaron',
greet: function() {
// 因為是 person 這個物件呼叫了 greet(),所以 this 指向 person
console.log(`Hello, my name is ${this.name}.`);
}
};
person.greet(); // 輸出 "Hello, my name is Aaron."即使是將方法賦值給另一個變數,this 的指向也會根據最終是誰呼叫它而改變。
const person = {
name: 'Aaron',
greet: function() {
console.log("Hello, my name is " + this.name); // 輸出 Aaron, 因為 this 指向 person
}
};
person.greet(); // 輸出 "Hello, my name is Aaron." (在物件方法中呼叫,this 指向 person)
const sayHello = person.greet;
sayHello(); // 輸出 "Hello, my name is undefined." (在嚴格模式下,因為此時是全域呼叫,this 是 undefined)4. 在箭頭函式中
箭頭函式 (=>) 是 ES6 的一個重要特性,它處理 this 的方式與傳統函式完全不同。
箭頭函式沒有自己的 this。它會捕獲其定義時所在的外部作用域的 this 值作為自己的 this。
我們先看以下這個例子:
'use strict';
const person = {
name: 'Aaron',
hobbies: ['Coding', 'Reading', 'Gaming'],
showHobbies: function() {
const self = this; // 保存外層的 this
// 在 showHobbies 方法中,this 指向 person
this.hobbies.forEach(function(hobby) {
// 在 forEach 的回呼函式中,this 是 undefined (嚴格模式)
console.log(`${this.name} likes ${hobby}.`); // 這裡會報錯
console.log(`${self.name} likes ${hobby}.`); 使用 self 來引用外層的 this
});
},
};
person.showHobbies();- 在
showHobbies方法中對this.hobbies使用forEach迴圈,想要印出person的name和每個hobby。 - 在
showHobbies中this指向person物件,但在forEach的回呼函式中,this跟外層的this不同,它會指向到undefined,所以console.log(`${this.name} likes ${hobby}.`);的this.name會報錯。 - 為了解決這個問題,可以宣告一個
self變數來保存外層的this,然後在forEach中使用self。 - 但這樣就必須手動處理
this的指向,這樣會讓程式碼變得冗長。
改用箭頭函式
現在我們來看看如何使用箭頭函式來簡化這個問題:
'use strict';
const person = {
name: 'Aaron',
hobbies: ['Coding', 'Reading', 'Gaming'],
showHobbies: function() {
const self = this; // 使用箭頭函式就不需要這一行了
this.hobbies.forEach((hobby) => {
console.log(`${this.name} likes ${hobby}.`); // 這裡的 this 會指向 person 了
console.log(`${self.name} likes ${hobby}.`); 使用 self 來引用外層的 this
});
},
};
person.showHobbies();- 改用箭頭函式之後就不需要再宣告
self變數了,因為箭頭函式會自動捕獲外層的this。 - 在
forEach的回呼函式中,this會指向showHobbies方法被呼叫時的this,也就是person物件。 - 所以可以直接使用
this.name來取得person的name。
小結
this 的指向是 JavaScript 中一個相對複雜但極為重要的概念。掌握它能讓你更深入地理解物件導向程式設計和函式執行的原理。
- 物件方法:
this指向呼叫該方法的物件。 - 箭頭函式:
this是定義時外層作用域的this。 - 一般函式:
this在嚴格模式下是undefined。 - 手動綁定: 可以使用
bind,call,apply來強制指定this。
範例練習
預測以下程式碼的輸出結果。
javascriptconst car = { brand: 'Tesla', getModel: function() { console.log(this.brand); } }; const getBrand = car.getModel; getBrand(); // 這裡會輸出什麼?如何修正上一題的程式碼,讓
getBrand()能夠正確輸出 "Tesla"?(至少提供兩種方法)解釋為什麼以下兩段程式碼的輸出結果不同。
javascript// 程式碼 A const counterA = { count: 0, increment: function() { setTimeout(function() { console.log(this.count); }, 1000); } }; counterA.increment(); // 程式碼 B const counterB = { count: 0, increment: function() { setTimeout(() => { console.log(this.count); }, 1000); } }; counterB.increment();
