本文 AI 產出,尚未審核

TypeScript – 函式(Functions)

主題:箭頭函式與 this


簡介

在 JavaScript 以及 TypeScript 中,函式是最基本的抽象單位。隨著 ES6 的推出,箭頭函式(Arrow Function)成為了撰寫函式的新風格,它不僅語法更簡潔,還對 this 的綁定行為做了根本性的改變。

對於 初學者 來說,箭頭函式看起來只是一種寫法的糖衣,但在 實務開發 中,正確掌握它與 this 的互動,往往是避免錯誤、提升程式可讀性的關鍵。本篇文章將從概念、範例、常見陷阱到最佳實踐,完整說明「箭頭函式與 this」在 TypeScript 中的使用方式,幫助你在日常開發中游刃有餘。


核心概念

1. 箭頭函式的語法與特性

形式 範例 說明
省略 function 關鍵字 const add = (a: number, b: number) => a + b; 直接使用 => 定義函式。
隱式回傳 const square = (n: number) => n * n; 若函式體只有單一表達式,可省略大括號與 return
多行程式碼 typescript\nconst log = (msg: string) => {\n console.log(msg);\n return true;\n};\n 需要大括號時,必須自行 return
參數型別推斷 const greet = name => Hello, ${name}; TypeScript 會根據使用情境推斷型別(若無明確宣告)。

重點:箭頭函式本身 不會擁有自己的 thisargumentssupernew.target,它會「捕獲」外層執行環境的 this(稱為 lexical this)。

2. this 在傳統函式 vs. 箭頭函式

class Counter {
  count = 0;

  // 傳統函式
  increase() {
    setTimeout(function () {
      // 這裡的 `this` 為 `window`(或 undefined,在 strict mode)
      this.count++; // ❌ 會出錯
    }, 100);
  }

  // 箭頭函式
  increaseArrow() {
    setTimeout(() => {
      // 這裡的 `this` 直接捕獲自 `increaseArrow` 的執行環境
      this.count++; // ✅ 正常
    }, 100);
  }
}
  • 傳統函式:在被 setTimeoutsetInterval、事件處理器等「脫離」原本呼叫上下文的情況下,this 會被重新綁定(預設指向全域物件或 undefined)。
  • 箭頭函式this 會固定為宣告時所在的外層物件,不會因為呼叫方式改變而變動,因此常被用來解決 this 失效的問題。

3. TypeScript 中的 this 型別

TypeScript 允許在函式宣告時明確指定 this 的型別,這對於 普通函式 仍然很有用:

function logThis(this: { name: string }) {
  console.log(this.name);
}
const obj = { name: 'Alice', log: logThis };
obj.log(); // Alice

箭頭函式,因為 this 是 lexical,不能自行指定型別;若需要在類別內使用,直接依賴類別本身的型別即可。

4. 何時該使用箭頭函式?

情境 建議使用 為什麼
事件處理器(如 addEventListener ✅ 箭頭函式 捕獲外層 this,避免 event.targetthis 混淆。
陣列方法的回呼mapfilterreduce ✅ 箭頭函式 簡潔、this 不需要變更。
需要 new 建立實例 ❌ 傳統函式 箭頭函式沒有 [[Construct]],不可作為建構子。
需要 arguments 物件 ❌ 傳統函式 箭頭函式沒有 arguments,若需可使用 rest 參數。

5. 範例彙整

範例 1:簡易計數器(使用箭頭函式避免 this 失效)

class Timer {
  private seconds = 0;
  private id?: number;

  start() {
    // 使用箭頭函式,this 永遠指向 Timer 實例
    this.id = window.setInterval(() => {
      this.seconds++;
      console.log(`已經過 ${this.seconds} 秒`);
    }, 1000);
  }

  stop() {
    if (this.id !== undefined) {
      clearInterval(this.id);
    }
  }
}
const t = new Timer();
t.start();
// 5 秒後呼叫 t.stop();

範例 2:陣列 map 中的 this 捕獲

interface Person {
  name: string;
  age: number;
}
const people: Person[] = [
  { name: 'Tom', age: 28 },
  { name: 'Jane', age: 22 },
];

class Formatter {
  prefix = '>>';

  format = (p: Person) => `${this.prefix} ${p.name} (${p.age})`;
  // 這裡的 this 直接來自 Formatter 實例
}

const f = new Formatter();
const result = people.map(f.format);
console.log(result); // [ '>> Tom (28)', '>> Jane (22)' ]

範例 3:事件監聽器中的 this(DOM 範例)

const btn = document.createElement('button');
btn.textContent = '點我';
document.body.appendChild(btn);

class Clicker {
  count = 0;

  // 使用箭頭函式,使 this 永遠指向 Clicker 實例
  handleClick = (e: MouseEvent) => {
    this.count++;
    console.log(`已點擊 ${this.count} 次`);
  };
}

const c = new Clicker();
btn.addEventListener('click', c.handleClick);

範例 4:需要 arguments 時的解法

function sum(...nums: number[]) {
  // 使用 rest 參數取代舊式的 arguments
  return nums.reduce((a, b) => a + b, 0);
}
console.log(sum(1, 2, 3, 4)); // 10

備註:若一定要在箭頭函式裡取得呼叫時的參數,請使用 rest 參數,因為箭頭函式沒有自己的 arguments 物件。

範例 5:建構子函式不能是箭頭函式

// ❌ 錯誤寫法:箭頭函式無法當作建構子
const Person = (name: string) => {
  this.name = name;
};
// const p = new Person('Alice'); // TypeError

// ✅ 正確寫法:使用傳統函式或 class
class PersonClass {
  constructor(public name: string) {}
}
const p = new PersonClass('Alice');
console.log(p.name); // Alice

常見陷阱與最佳實踐

陷阱 說明 解決方式
this 仍被覆寫 在某些情況下,外層 this 本身就不正確(例如把方法直接傳給別的物件) 使用 .bind(this)const fn = this.method.bind(this),或改寫成箭頭屬性(如 method = () => {})。
誤以為箭頭函式可以使用 new 箭頭函式沒有 [[Construct]]],會拋出錯誤 必須使用 classfunction 宣告建構子。
忘記 return(多行程式碼) 大括號包住的箭頭函式需要手動 return 確認是否需要回傳值,必要時加上 return,或改寫成單行隱式回傳。
使用 arguments 箭頭函式沒有 arguments 物件 改用 rest 參數 (...args)。
在類別原型上使用箭頭函式 會在每次建立實例時重新建立函式,增加記憶體負擔 若函式不需要捕獲 this,可使用 原型方法method(){})以共享同一函式實例。

最佳實踐

  1. 只在需要 lexical this 時使用箭頭函式。若函式不依賴 this,傳統方法更具可讀性且效能稍佳。
  2. 在類別屬性上宣告箭頭函式(如 method = () => {})可確保每個實例都有正確的 this,但需留意記憶體使用。
  3. 使用 strict 模式"use strict")避免因 this 變成全域物件而產生隱蔽錯誤。
  4. 在 TypeScript 中加入 this 型別(只對普通函式有效),提升編譯時的安全性。
  5. 避免在大型迭代或高頻呼叫的路徑內建立大量箭頭函式,可改為事先定義好方法或使用 bind

實際應用場景

1. 前端框架的事件綁定(React、Vue)

  • React 中的類別元件常以箭頭函式寫成事件處理器,避免在 render 時每次都重新 bind

    class MyButton extends React.Component {
      state = { count: 0 };
    
      // Arrow property ensures correct `this`
      handleClick = () => {
        this.setState({ count: this.state.count + 1 });
      };
    
      render() {
        return <button onClick={this.handleClick}>點擊 {this.state.count}</button>;
      }
    }
    
  • Vue 3 的 Composition API 內部大量使用箭頭函式,讓 setup 中的 this 不會被意外改寫。

2. Node.js 中的非同步流程

async/await 與 Promise 鏈結中,箭頭函式保持 this 直屬於外層物件,讓服務端程式碼更易讀:

class DB {
  private client: any;
  constructor(client: any) { this.client = client; }

  getUser(id: string) {
    return this.client.query('SELECT * FROM users WHERE id = $1', [id])
      .then(row => {
        // Arrow keeps `this` = DB instance
        console.log('查詢完成於', this);
        return row;
      });
  }
}

3. 高階函式(Higher‑Order Functions)

在函式式程式設計中,常會傳遞回呼給其他函式,例如 debouncethrottle。使用箭頭函式可以保證 this 不會因包裝層而失效:

function debounce<T extends (...args: any[]) => any>(fn: T, delay: number) {
  let timer: NodeJS.Timeout;
  return (...args: Parameters<T>) => {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}

總結

  • 箭頭函式語法簡潔lexical this 為核心特性,是現代 TypeScript/JavaScript 開發的常用工具。
  • 正確理解 this 捕獲機制,能在非同步、事件處理、陣列操作等情境中避免常見的 this 失效錯誤。
  • 仍有 限制(無 new、無 arguments、每個實例會產生新函式),使用前應評估效能與記憶體需求。
  • 實務專案(前端框架、Node.js 服務、函式式程式設計)中,適當選擇箭頭函式或傳統函式,搭配 TypeScript 的型別檢查,可提升程式碼的安全性與可維護性。

掌握了箭頭函式與 this 的互動原理,你就能在撰寫 TypeScript 程式時更加得心應手,寫出 乾淨、可靠且易於維護 的程式碼。祝開發順利!