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只能在父類別屬性是 protected 或 public 時存取。super仍然是 指向當前實例,所以this.age與super.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 成員在子類別不可見,會編譯錯誤。 |
使用 protected 或 public,或提供 protected 的輔助方法。 |
| 多層繼承時忘記傳遞必要參數 | 父類別建構子參數不完整會導致編譯錯誤或執行時行為不正確。 | 為每層建構子寫清楚的 參數映射表,或使用 介面 (interface) 來統一。 |
在靜態方法中使用 super |
靜態成員只能透過 super 存取父類別的 靜態 成員。 |
確認呼叫的是 靜態 方法或屬性;若要存取實例成員,必須在實例方法內使用。 |
最佳實踐
- 保持建構子簡潔:只負責初始化屬性,業務邏輯盡量放在方法中。
- 使用
protected而非public:讓子類別能存取,同時避免外部直接修改。 - 在覆寫方法時先呼叫
super(除非確定要完全取代),這樣可以保留父類別的基礎行為。 - 寫測試:特別是繼承層級較深的類別,確保
super呼叫的順序與參數正確。 - 盡量避免過深的繼承:若需要多層次行為,考慮使用 Mixins 或 Composition,可降低
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 包含 id、createdAt、updatedAt 等欄位。子類別透過 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 專案在 可讀性、可擴充性與穩定性 上都有顯著提升。祝你寫程式愉快,持續在物件導向的道路上前進!