本文 AI 產出,尚未審核

JavaScript 原型與繼承:super 與繼承機制


簡介

在 JavaScript 中,原型(prototype) 是所有物件共享行為的根本機制,而 繼承 則讓我們可以在不複製程式碼的前提下,建立功能更完整的類別(class)。ES6 引入的 class 語法只是語法糖,背後仍舊是原型鏈的運作。

其中最常讓新手感到疑惑的,就是 super 關鍵字。它不僅能在子類別的建構子(constructor)裡呼叫父類別的建構子,還能在方法中存取父類別原型上的實作。正確掌握 super 的使用方式,能讓我們寫出 可讀性高、維護性好的繼承程式碼,在大型專案或框架開發時尤其重要。

本篇文章將從概念說明、實作範例、常見陷阱與最佳實踐,直到實務應用,完整闡述 super 與 JavaScript 繼承機制,幫助讀者從「會用」走向「懂原理」的階段。


核心概念

1. classextends 與原型鏈

class Animal {
  constructor(name) {
    this.name = name;
  }
  speak() {
    console.log(`${this.name} makes a sound.`);
  }
}
  • class 定義的是 構造函式(constructor)以及掛在其 prototype 上的方法。
  • extends 會在子類別的原型上建立 [[Prototype]](即 [[Prototype]])指向父類別的 prototype,形成 原型鏈
class Dog extends Animal {
  // 這裡會自動建立 Dog.prototype.__proto__ === Animal.prototype
}

重點:即使使用 class,底層仍是 原型繼承instanceofObject.getPrototypeOf 等檢查方式不會因語法糖而改變。

2. super 在建構子(constructor)中的角色

在子類別的建構子裡,必須先呼叫 super(),才能使用 thissuper() 會執行父類別的建構子,並把 this 綁定到子類別的實例上。

class Cat extends Animal {
  constructor(name, color) {
    // 必須先呼叫 super,才能使用 this
    super(name);               // 呼叫 Animal 的 constructor
    this.color = color;        // 接著才能設定子類別自己的屬性
  }
}
  • 若未呼叫 super(),執行時會拋出 ReferenceError: Must call super constructor before using 'this' in derived class constructor

3. super 在方法中的使用

super 也可以在 普通方法(包括 getter、setter)裡呼叫父類別原型上的同名方法。此時 super 代表 父類別的 prototype,而非父類別的建構子。

class Bird extends Animal {
  speak() {
    // 先執行父類別的 speak,再加上自己的行為
    super.speak();                 // 呼叫 Animal.prototype.speak
    console.log(`${this.name} chirps.`);
  }
}
  • super 只能在 類別方法內 使用,若在普通函式(function)或箭頭函式外部使用,會得到 ReferenceError

4. super 與靜態方法(static)

靜態方法屬於類別本身,而非實例。使用 super 於靜態方法時,指向的是 父類別本身(即父類別的 constructor),而非父類別的 prototype。

class Vehicle {
  static describe() {
    console.log('All vehicles have wheels.');
  }
}

class Car extends Vehicle {
  static describe() {
    super.describe();               // 呼叫 Vehicle.describe
    console.log('Cars can transport people.');
  }
}

5. superObject.setPrototypeOfObject.create 的關係

在 ES5 以前,我們常透過 Object.create 或手動設定 prototype 來實作繼承。super 的底層實作,其實就是 把子類別的 prototype 指向父類別的 prototype,而在建構子裡呼叫 super(),則相當於 執行父類別的建構子,並把返回的 this 交給子類別。

// ES5 手寫繼承(等同於 class extends)
function Person(name) {
  this.name = name;
}
Person.prototype.greet = function () {
  console.log(`Hi, I'm ${this.name}`);
};

function Student(name, school) {
  Person.call(this, name);          // super(name)
  this.school = school;
}
Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;
Student.prototype.study = function () {
  console.log(`${this.name} studies at ${this.school}`);
};

程式碼範例

範例 1:基本的 super 呼叫(建構子)

class Shape {
  constructor(color) {
    this.color = color;
  }
  describe() {
    console.log(`A ${this.color} shape.`);
  }
}

class Circle extends Shape {
  constructor(color, radius) {
    super(color);           // 呼叫 Shape 的 constructor
    this.radius = radius;
  }
  area() {
    return Math.PI * this.radius ** 2;
  }
  describe() {
    super.describe();       // 先執行父類別的 describe
    console.log(`It's a circle with radius ${this.radius}.`);
  }
}

const c = new Circle('red', 5);
c.describe();
// A red shape.
// It's a circle with radius 5.

說明super(color)color 傳給父類別,子類別仍然可以自行加入 radius。在 describe 方法裡,super.describe() 讓我們不必重複父類別的敘述。

範例 2:super 在 getter / setter 中的應用

class Rectangle {
  constructor(width, height) {
    this._width = width;
    this._height = height;
  }
  get area() {
    return this._width * this._height;
  }
}

class Square extends Rectangle {
  constructor(side) {
    super(side, side);  // 正方形的寬高相同
  }
  // 重新定義 getter,仍保留父類別的計算方式
  get area() {
    console.log('Calculating area of a square...');
    return super.area;   // 呼叫 Rectangle.prototype.area
  }
}

const s = new Square(4);
console.log(s.area);
// Calculating area of a square...
// 16

說明:即使在 getter 裡,super.area 仍然可以存取父類別的計算邏輯,避免重複實作。

範例 3:靜態方法中的 super

class Logger {
  static log(message) {
    console.log(`[LOG] ${message}`);
  }
}

class FileLogger extends Logger {
  static log(message) {
    super.log(message);                     // 呼叫 Logger.log
    // 實際上會寫入檔案,這裡用 console 代替
    console.log(`[FILE] ${message}`);
  }
}

FileLogger.log('系統啟動');
// [LOG] 系統啟動
// [FILE] 系統啟動

說明:靜態方法的 super 指向父類別本身(Logger),因此可以在子類別的靜態方法中「先執行父類別的行為,再加入自己的額外功能」。

範例 4:supernew.target 結合(進階)

class Base {
  constructor() {
    console.log('Base constructor, new.target:', new.target.name);
  }
}
class Derived extends Base {
  constructor() {
    super();                           // Base 的 constructor 會看到 new.target 為 Derived
    console.log('Derived constructor');
  }
}
new Derived();
// Base constructor, new.target: Derived
// Derived constructor

說明new.target 會在父類別的建構子裡指向實際被 new 的子類別,這對於 抽象類別工廠模式 的實作非常有用。

範例 5:多層繼承與 super 的鏈式呼叫

class A {
  method() {
    console.log('A');
  }
}
class B extends A {
  method() {
    console.log('B');
    super.method();   // 呼叫 A.method
  }
}
class C extends B {
  method() {
    console.log('C');
    super.method();   // 呼叫 B.method,B 再呼叫 A.method
  }
}
new C().method();
// C
// B
// A

說明super 會沿著 原型鏈 向上搜尋同名方法,形成 鏈式呼叫,這在 Mixin行為疊加 時相當便利。


常見陷阱與最佳實踐

陷阱 可能的錯誤 解決方式 / 最佳實踐
未先呼叫 super() ReferenceError: Must call super constructor before using 'this' 在子類別建構子最前面 必須 呼叫 super(),即使父類別沒有參數也要寫 super();
在箭頭函式中使用 super ReferenceError: super 或行為不如預期 super 只能在 普通方法 中使用,若需要在回呼裡使用,請改用 function 或先把 super 方法存到變數(const parentMethod = super.method;
靜態方法與實例方法混用 super 呼叫不到預期的父類別方法 靜態方法的 super 指向父類別本身,實例方法的 super 指向父類別 prototype。保持概念分離,必要時使用 ClassName.method.call(this, ...)
多重繼承(Mixin)中 super 的衝突 方法被意外覆寫或呼叫錯誤的父方法 使用 Mixin 函式(例如 Object.assign)時,避免在同一層級使用 super;若必須,採用 Composition over Inheritance 的設計
忘記設定子類別的 constructor 子類別會自動使用父類別的建構子,導致 this 屬性缺失 若子類別需要自己的屬性,一定要寫建構子並呼叫 super();若不需要,保留預設建構子即可

最佳實踐

  1. 只在需要時使用 super:若子類別完全覆寫父類別的方法,且不需要父類別的行為,則不必呼叫 super,減少不必要的執行成本。
  2. 保持建構子簡潔:建構子只負責 初始化屬性,把業務邏輯搬到其他方法,讓 super 的呼叫保持單一職責。
  3. 使用 super 取代手動原型操作:避免直接寫 Child.prototype = Object.create(Parent.prototype),除非要支援非常舊的環境。
  4. 在靜態方法中使用 super 前先確認父類別是否有相同的靜態成員,避免意外的 undefined 錯誤。
  5. 測試多層繼承:使用單元測試驗證每一層 super 呼叫的行為,確保未因重構而破壞鏈式呼叫。

實際應用場景

  1. UI 元件庫

    • 基底元件(例如 Component)提供生命週期方法 mountunmount。子元件在覆寫 mount 時,仍需要呼叫 super.mount() 以完成基礎的 DOM 初始化。
  2. 錯誤處理與自訂例外

    • 自訂錯誤類別繼承自 Error,在建構子裡使用 super(message),同時加入自訂屬性(如 codestatus),讓錯誤資訊更完整。
  3. 資料模型與 ORM

    • 基礎模型提供通用的 CRUD 方法。子模型(如 UserPost)只需覆寫或擴充特定查詢,並使用 super.save() 以保留共通的儲存流程。
  4. 服務端框架(Express、Koa)

    • 中間件基底類別實作 handle(req, res, next),子類別可在呼叫 super.handle 前後加入前置或後置處理,形成 可組合的管線
  5. 遊戲開發

    • 角色(Character)基底類別提供 moveattack 等方法。不同類型的角色(WarriorMage)在覆寫 attack 時,用 super.attack() 先執行共通的傷害計算,再加入自己的特殊效果。

總結

  • superES6 class 中連結子類別與父類別的關鍵橋樑,分別在 建構子實例方法getter/setter 以及 靜態方法 中扮演不同角色。
  • 它的底層機制仍是 原型鏈,只是在語法層面提供了更直覺、可讀性高的呼叫方式。
  • 正確使用 super 能避免 重複程式碼、提升 維護性,同時在多層繼承時保證 行為的正確疊加
  • 常見的錯誤多與 忘記呼叫 super()在錯誤的上下文使用 super 有關,遵守「先呼叫 super 再使用 this」的原則即可避免大部分問題。
  • 在實務開發中,從 UI 元件、錯誤處理、資料模型到遊戲角色等各種場景,都能看到 super 與繼承的身影。掌握它不僅是語法層面的需求,更是 設計可擴充、可維護系統 的基礎。

實踐建議:在新專案中,先以 class + extends + super 為主,除非真的需要兼容非常舊的瀏覽器,才回退到手動操作 prototype。持續寫單元測試,確保每一次繼承的變更都不會破壞既有行為。

祝你在 JavaScript 的原型與繼承世界裡寫出更乾淨、更強大的程式碼! 🚀