Skip to content

this 關鍵字

在 JavaScript 中,this 是一個非常特別且強大的關鍵字,但同時也常常讓人感到困惑。

this 的值取決於它被呼叫時的 執行上下文 (Execution Context) 的位置。

簡單來說,this 就是「呼叫我的人」。

是誰執行了這段程式碼,this 就指向誰。

this 的指向規則

this 的值主要可以根據以下幾種情況來判斷:

1. 在全域作用域中

  • 在瀏覽器環境中

    this 指向 window

javascript
// 在瀏覽器中會輸出 Window 物件
console.log(this); // Window: { console, alert, confirm, navigator, ... }
  • 在 Node.js 環境中

    this 指向 globalThis,預設是一個空物件 {}

javascript
// 在 Node.js 中會輸出 global 物件
console.log(this); // {}

同時根據 作用域#補充變數章節沒有提到的-var-let-const-差異var 宣告的變數會被提升到全域作用域,所以當在全域作用域中使用 var 宣告變數時,這些變數同時也會成為全域 this 物件的屬性。

javascript
// 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); // true

2. 在一般函式中

在一般函式(非物件方法、非箭頭函式)中直接呼叫時,this 的值會有點棘手,它會因為是否開啟了嚴格模式而有所不同。

嚴格模式

嚴格模式是 JavaScript 的一種運行模式,可以通過在程式碼的開頭加上 'use strict'; 來啟用。

它會讓 JavaScript 的一些行為變得更嚴格,避免一些常見的錯誤。

用一個表格來說明會比較清晰:

環境嚴格模式 ✅嚴格模式 ❌
瀏覽器undefinedwindow
Node.jsundefinedglobalThis
javascript
// 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 開發通常預設或建議使用嚴格模式,以避免一些意料之外的行為。所以你可以傾向於記住:在一般函式中,thisundefined

3. 作為物件的方法

這是最常見也最直觀的情況:當函式作為一個物件的方法被呼叫時,this 會指向該物件

javascript
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 的指向也會根據最終是誰呼叫它而改變。

javascript
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

我們先看以下這個例子:

javascript
'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();
  1. showHobbies 方法中對 this.hobbies 使用 forEach 迴圈,想要印出 personname 和每個 hobby
  2. showHobbiesthis 指向 person 物件,但在 forEach 的回呼函式中,this 跟外層的 this 不同,它會指向到 undefined,所以 console.log(`${this.name} likes ${hobby}.`);this.name 會報錯。
  3. 為了解決這個問題,可以宣告一個 self 變數來保存外層的 this,然後在 forEach 中使用 self
  4. 但這樣就必須手動處理 this 的指向,這樣會讓程式碼變得冗長。

改用箭頭函式

現在我們來看看如何使用箭頭函式來簡化這個問題:

javascript
'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();
  1. 改用箭頭函式之後就不需要再宣告 self 變數了,因為箭頭函式會自動捕獲外層的 this
  2. forEach 的回呼函式中,this 會指向 showHobbies 方法被呼叫時的 this,也就是 person 物件。
  3. 所以可以直接使用 this.name 來取得 personname

小結

this 的指向是 JavaScript 中一個相對複雜但極為重要的概念。掌握它能讓你更深入地理解物件導向程式設計和函式執行的原理。

  • 物件方法: this 指向呼叫該方法的物件。
  • 箭頭函式: this 是定義時外層作用域的 this
  • 一般函式: this 在嚴格模式下是 undefined
  • 手動綁定: 可以使用 bind, call, apply 來強制指定 this

範例練習

  1. 預測以下程式碼的輸出結果。

    javascript
    const car = {
      brand: 'Tesla',
      getModel: function() {
        console.log(this.brand);
      }
    };
    
    const getBrand = car.getModel;
    getBrand(); // 這裡會輸出什麼?
  2. 如何修正上一題的程式碼,讓 getBrand() 能夠正確輸出 "Tesla"?(至少提供兩種方法)

  3. 解釋為什麼以下兩段程式碼的輸出結果不同。

    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();

Wrirten by Aaron Su