本文 AI 產出,尚未審核

TypeScript — 類別(Classes)

主題:super 與父類別呼叫


簡介

在物件導向程式設計中,類別的繼承讓我們可以把共用的行為抽象到父類別,子類別只需要關注自己的特色。
super 是 TypeScript(以及 ES6+)提供的關鍵字,用來存取或呼叫父類別的建構子、方法或屬性。正確使用 super 能讓程式碼保持 DRY(Don't Repeat Yourself)且易於維護。

本單元將說明 super 的語法與執行時機,並透過多個實務範例展示 如何在子類別中正確呼叫父類別,讓你在開發大型前端或 Node.js 專案時,能夠自信地使用繼承機制。


核心概念

1️⃣ super 的基本語意

用法 說明
super() 在子類別的建構子 呼叫父類別的建構子(必須在 this 被使用前執行)。
super.methodName(...args) 呼叫父類別中 已被覆寫 的方法。
super.prop 取得或設定父類別的 屬性(在屬性不是 private 時)。

注意super 只能在 類別內部 使用,且只能在 子類別extends 的類別)中出現。


2️⃣ 在建構子中呼叫父類別 (super())

class Animal {
  protected name: string;

  constructor(name: string) {
    this.name = name;
    console.log(`Animal 建構完成:${this.name}`);
  }

  move(distance: number) {
    console.log(`${this.name} 移動了 ${distance} 公尺`);
  }
}

/* 子類別必須先呼叫 super(),才能使用 this */
class Dog extends Animal {
  private breed: string;

  constructor(name: string, breed: string) {
    // 先呼叫父類別建構子,確保 name 被正確初始化
    super(name);
    this.breed = breed;
    console.log(`Dog 建構完成:${this.name} (${this.breed})`);
  }
}

const d = new Dog('小白', '柴犬');
// 輸出:
// Animal 建構完成:小白
// Dog 建構完成:小白 (柴犬)

關鍵點

  • super(name) 必須this 被使用前(例如 this.breed = …)先執行。
  • 若父類別建構子需要參數,子類別必須把對應參數傳遞給 super

3️⃣ 呼叫父類別的被覆寫方法

class Vehicle {
  start() {
    console.log('Vehicle 啟動');
  }
}

class Car extends Vehicle {
  // 覆寫 start 方法
  start() {
    console.log('Car 準備啟動...');
    // 呼叫父類別的 start,保留原有行為
    super.start();
    console.log('Car 已啟動');
  }
}

new Car().start();
// 輸出:
// Car 準備啟動...
// Vehicle 啟動
// Car 已啟動

使用 super.start() 可以 保留父類別的行為,同時在子類別加入額外的邏輯,這是「擴充」而非「取代」的典型寫法。


4️⃣ 透過 super 存取父類別屬性

class Person {
  protected age: number = 0;

  celebrateBirthday() {
    this.age++;
    console.log(`今年 ${this.age} 歲`);
  }
}

class Student extends Person {
  // 想在生日慶祝時,先顯示「學生」的訊息,再執行父類別的邏輯
  celebrateBirthday() {
    console.log('學生生日快樂!');
    // 呼叫父類別的屬性與方法
    super.celebrateBirthday();   // this.age 仍指向子類別的實例
  }
}

new Student().celebrateBirthday();
// 輸出:
// 學生生日快樂!
// 今年 1 歲

說明

  • super.age 只能在父類別屬性是 protectedpublic 時存取。
  • super 仍然是 指向當前實例,所以 this.agesuper.age 其實指向同一個欄位,只是存取的語法不同。

5️⃣ 多層繼承與 super(深入一層)

class Shape {
  constructor(public color: string) {}
  describe() {
    console.log(`這是一個 ${this.color} 的形狀`);
  }
}

class Rectangle extends Shape {
  constructor(color: string, public width: number, public height: number) {
    super(color);                     // 呼叫 Shape 的建構子
  }
  describe() {
    super.describe();                 // 呼叫 Shape.describe()
    console.log(`寬 ${this.width},高 ${this.height}`);
  }
}

class Square extends Rectangle {
  constructor(color: string, size: number) {
    // Rectangle 需要 width、height,皆傳入 size
    super(color, size, size);
  }
  // 直接使用 Rectangle.describe,若需要再加自己的行為,可再呼叫 super.describe()
}

new Square('紅色', 5).describe();
// 輸出:
// 這是一個 紅色 的形狀
// 寬 5,高 5

重點

  • 每一層子類別都必須 自行呼叫一次 super(...),才能正確鏈結到最終的父類別建構子。
  • 在多層繼承中,super 只會指向 直接的父類別,若想要呼叫更上層的行為,需要在父類別內部再使用 super

常見陷阱與最佳實踐

陷阱 說明 建議的做法
忘記在子類別建構子呼叫 super() 會拋出 ReferenceError: Must call super constructor before accessing 'this' 只要子類別有 constructor第一行必須是 super(...args)
super() 前使用 this 同上,會造成執行時錯誤。 把所有對 this 的操作移到 super() 之後。
呼叫 super 的方法被 private 修飾 private 成員在子類別不可見,會編譯錯誤。 使用 protectedpublic,或提供 protected 的輔助方法。
多層繼承時忘記傳遞必要參數 父類別建構子參數不完整會導致編譯錯誤或執行時行為不正確。 為每層建構子寫清楚的 參數映射表,或使用 介面 (interface) 來統一。
在靜態方法中使用 super 靜態成員只能透過 super 存取父類別的 靜態 成員。 確認呼叫的是 靜態 方法或屬性;若要存取實例成員,必須在實例方法內使用。

最佳實踐

  1. 保持建構子簡潔:只負責初始化屬性,業務邏輯盡量放在方法中。
  2. 使用 protected 而非 public:讓子類別能存取,同時避免外部直接修改。
  3. 在覆寫方法時先呼叫 super(除非確定要完全取代),這樣可以保留父類別的基礎行為。
  4. 寫測試:特別是繼承層級較深的類別,確保 super 呼叫的順序與參數正確。
  5. 盡量避免過深的繼承:若需要多層次行為,考慮使用 MixinsComposition,可降低 super 相關的維護成本。

實際應用場景

1️⃣ UI 元件庫的基礎類別

在開發一套自訂 UI 元件庫時,常會有 BaseComponent 提供共用的生命週期、樣式管理與事件綁定。各個具體元件(Button、Modal、Tooltip)繼承自 BaseComponent,並在建構子或 render 方法中呼叫 super 以完成基礎初始化。

abstract class BaseComponent {
  protected element: HTMLElement;

  constructor(tag: keyof HTMLElementTagNameMap = 'div') {
    this.element = document.createElement(tag);
    this.initStyle();
  }

  protected initStyle() {
    this.element.style.boxSizing = 'border-box';
  }

  abstract render(): void;
}

class Button extends BaseComponent {
  constructor(private label: string) {
    super('button');          // 產生 <button> 元素
  }

  render() {
    super.initStyle();        // 確保基礎樣式已套用
    this.element.textContent = this.label;
    document.body.appendChild(this.element);
  }
}

2️⃣ 伺服器端的錯誤處理階層

在 Node.js + TypeScript 的後端專案中,常會建立 BaseError(包含錯誤碼、訊息),讓所有自訂錯誤類別繼承它。子類別在建構子裡使用 super(message, code),保證錯誤資訊一致。

class BaseError extends Error {
  constructor(public message: string, public code: number) {
    super(message);
    this.name = this.constructor.name;
  }
}

class NotFoundError extends BaseError {
  constructor(resource: string) {
    super(`${resource} not found`, 404);
  }
}

3️⃣ 資料模型的繼承與驗證

在使用 ORM(如 TypeORM)時,常會有 BaseEntity 包含 idcreatedAtupdatedAt 等欄位。子類別透過 super() 讓這些欄位自動加入,並在子類別裡覆寫 validate() 方法,同時呼叫 super.validate() 完成基礎驗證。

abstract class BaseEntity {
  id!: number;
  createdAt!: Date;
  updatedAt!: Date;

  validate(): string[] {
    const errors: string[] = [];
    if (!this.id) errors.push('Missing id');
    return errors;
  }
}

class User extends BaseEntity {
  username!: string;
  email!: string;

  validate(): string[] {
    const errors = super.validate();   // 基礎檢查
    if (!this.email.includes('@')) errors.push('Invalid email');
    return errors;
  }
}

總結

  • super子類別與父類別溝通的橋樑,用於呼叫父類別的建構子、方法與屬性。
  • 在子類別的建構子中,必須先呼叫 super(),才能安全使用 this
  • 覆寫方法時,先呼叫 super.method() 能保留父類別的行為,僅在確定要完全取代時才省略。
  • 多層繼承時,每一層都要自行呼叫 super,且只能存取直接父類別的成員。
  • 常見錯誤包括忘記 super()、在 super() 前使用 this、以及對 private 成員的非法存取。
  • 實務上,super 常見於 UI 元件基底、錯誤類別階層、資料模型驗證等情境,能大幅減少重複程式碼、提升可維護性。

掌握 super 的正確使用方式,將讓你的 TypeScript 專案在 可讀性、可擴充性與穩定性 上都有顯著提升。祝你寫程式愉快,持續在物件導向的道路上前進!