本文 AI 產出,尚未審核

ES6+ 新特性:class 與繼承


簡介

在 JavaScript 的歷史演進中,class 於 ES6(ECMAScript 2015)正式登場,為物件導向程式設計(OOP)提供了更直觀、類似其他語言(如 Java、C#)的語法糖。雖然底層仍然是原型鏈(prototype)運作,使用 class 可以讓開發者以「類別」的概念去描述 資料行為,提升程式碼可讀性與維護性。

隨著前端框架(React、Vue、Angular)以及 Node.js 生態系的持續成長,繼承(inheritance)已成為構建可重用、可擴充模組的關鍵技術。掌握 ES6+ 的 class 與繼承,不僅能寫出更乾淨的程式碼,也能在團隊協作時減少誤解與錯誤。

本篇文章將從 class 的基本語法建構子與屬性方法與存取子(getter / setter)繼承與 super,以及 靜態屬性/方法 等核心概念切入,配合實用範例說明,最後彙整常見陷阱、最佳實踐與實務應用情境,協助你在日常開發中快速上手並避免踩雷。


核心概念

1. class 基礎語法

class 的宣告方式類似傳統語言的 class,但在 JavaScript 中它僅是 語法糖,最終仍會被編譯成原型鏈的操作。基本語法如下:

// 宣告一個簡單的類別
class Person {
  // 建構子 (constructor) 會在建立實例時自動執行
  constructor(name) {
    this.name = name; // 為每個實例設定 name 屬性
  }

  // 方法 (method) 會被放到 prototype 上
  greet() {
    console.log(`Hello, I'm ${this.name}.`);
  }
}

// 使用 class 建立實例
const alice = new Person('Alice');
alice.greet(); // => Hello, I'm Alice.
  • class 必須使用 new 關鍵字 來產生實例,直接呼叫 Person() 會拋出 TypeError
  • 所有方法都屬於 prototype,因此不會在每次建立實例時重新建立函式,節省記憶體。

2. 建構子與屬性

constructor 是類別的特殊方法,負責初始化實例屬性。若未自行定義,JavaScript 會自動產生一個空的建構子。

class Rectangle {
  // 建構子接受寬與高,並計算面積
  constructor(width, height) {
    this.width = width;
    this.height = height;
  }

  // 計算面積的實例方法
  area() {
    return this.width * this.height;
  }
}

// 建立一個 10x5 的矩形
const rect = new Rectangle(10, 5);
console.log(rect.area()); // => 50

小技巧:如果你只想在建構子裡做簡單的屬性賦值,可以直接使用參數屬性(parameter properties)結合 解構賦值,寫得更簡潔:

class Point {
  constructor({ x = 0, y = 0 } = {}) {
    Object.assign(this, { x, y });
  }
}
const p = new Point({ x: 3, y: 4 });
console.log(p); // => Point { x: 3, y: 4 }

3. 方法、getter / setter

除了普通方法外,ES6 允許在 class 中定義 gettersetter,讓屬性讀寫時能自動觸發額外邏輯。

class Circle {
  constructor(radius) {
    this._radius = radius; // 使用底線作為私有屬性慣例
  }

  // getter:取得直徑
  get diameter() {
    return this._radius * 2;
  }

  // setter:設定直徑時自動更新半徑
  set diameter(d) {
    this._radius = d / 2;
  }

  // 一般方法:計算面積
  area() {
    return Math.PI * this._radius ** 2;
  }
}

const c = new Circle(5);
console.log(c.diameter); // => 10
c.diameter = 20;         // 透過 setter 更新半徑
console.log(c.area());   // => 約 314.16
  • getter / setter 讓外部看起來像在直接操作屬性,實際上卻可以加入驗證、快取或副作用。
  • 注意:在 strict mode(ES6 模組預設即為 strict)下,若在 getter 中遺漏 return,會回傳 undefined,容易產生錯誤。

4. 繼承(extendssuper

extends 用於建立子類別,子類別會自動繼承父類別的 prototype 方法與屬性。super 則分為兩種情境:

  1. 在建構子內呼叫 super(...),必須在使用 this 前先呼叫父類別的建構子。
  2. 在方法內呼叫 super.methodName(),可存取父類別的同名方法(常用於「覆寫」)。
// 父類別:Vehicle
class Vehicle {
  constructor(make) {
    this.make = make;
  }

  start() {
    console.log(`${this.make} engine started.`);
  }
}

// 子類別:Car 繼承 Vehicle
class Car extends Vehicle {
  constructor(make, model) {
    // 必須先呼叫 super 才能使用 this
    super(make);
    this.model = model;
  }

  // 覆寫父類別的 start 方法,但仍想保留原有行為
  start() {
    super.start(); // 呼叫父類別的 start
    console.log(`${this.make} ${this.model} is ready to go!`);
  }
}

const myCar = new Car('Toyota', 'Corolla');
myCar.start();
// => Toyota engine started.
// => Toyota Corolla is ready to go!

多層繼承示例

class Animal {
  constructor(name) {
    this.name = name;
  }
  speak() {
    console.log(`${this.name} makes a sound.`);
  }
}

class Mammal extends Animal {
  constructor(name, hasFur = true) {
    super(name);
    this.hasFur = hasFur;
  }
}

class Dog extends Mammal {
  constructor(name, breed) {
    super(name);
    this.breed = breed;
  }
  speak() {
    // 只保留 Dog 的叫聲,不呼叫 super.speak()
    console.log(`${this.name} barks!`);
  }
}

const rover = new Dog('Rover', 'Golden Retriever');
rover.speak(); // => Rover barks!

重點:子類別若沒有自行定義建構子,會自動使用父類別的建構子;但一旦自行定義,就 必須 在最前面呼叫 super()(除非該類別不繼承任何類別)。


5. 靜態屬性與方法

static 關鍵字可將屬性或方法直接掛在類別本身,而非實例上。常用於工具函式、常量或「工廠方法」。

class MathUtil {
  // 靜態常量
  static PI = 3.1415926535;

  // 靜態方法
  static circleArea(radius) {
    return MathUtil.PI * radius ** 2;
  }
}

// 直接透過類別呼叫
console.log(MathUtil.PI);               // => 3.1415926535
console.log(MathUtil.circleArea(3));    // => 約 28.27

// 靜態方法不會出現在實例上
const util = new MathUtil();
console.log(util.circleArea); // => undefined
  • 靜態成員 不能被 extends 的子類別繼承成實例屬性,但子類別仍可 直接 透過 super 取得(例如 class Sub extends MathUtil {}Sub.PI 仍可存取)。
  • 常見用途:API 客戶端的 request 方法、資料模型的 findById、或是框架的 Component 基底類別的 render 靜態方法。

常見陷阱與最佳實踐

陷阱 說明 最佳實踐
忘記呼叫 super() 在子類別建構子中未先呼叫 super(),會在執行 this 前拋出 ReferenceError 永遠在使用 this 呼叫 super(...args),即使父類別建構子沒有參數,也要寫 super()
在 getter 裡寫副作用 Getter 被頻繁呼叫,若裡面執行大量計算或 I/O,會影響效能。 只在 getter 中返回值;如需額外計算,使用 memoization 或將邏輯搬到普通方法。
靜態方法與實例屬性混用 把應該是實例方法寫成 static,導致無法存取 this,或相反。 明確區分「屬於類別」與「屬於實例」的責任,遵循 Single Responsibility Principle
過度繼承 多層繼承使類別關係複雜,難以追蹤屬性來源。 優先考慮 組合(composition),如使用 mixin 或委派(delegation)。
未使用 #private 前 ES2022 只能靠命名慣例(底線)模擬私有屬性,仍可被外部存取。 若環境支援,使用 私有欄位 #field,提升封裝性與安全性。
// 私有欄位範例(ES2022+)
class Counter {
  #count = 0; // 真正的私有屬性

  increment() {
    this.#count++;
  }

  get value() {
    return this.#count;
  }
}
const c = new Counter();
c.increment();
console.log(c.value);   // => 1
console.log(c.#count);  // SyntaxError

實際應用場景

  1. 前端 UI 元件基底類別

    • 使用 class Component 定義 render()setState() 等通用方法,子元件繼承後只需實作自己的 UI。
  2. Node.js 服務的資料模型

    • class User extends Model 方式封裝資料庫 CRUD,讓每個模型只關心自己的 schema 與業務邏輯。
  3. 遊戲開發中的實體系統

    • class Entity 為所有遊戲物件的基礎,class Player extends Entityclass Enemy extends Entity 各自實作特有行為,並利用 super.update() 共享共通更新邏輯。
  4. API 客戶端的工廠模式

    • class ApiClient 包含 static createInstance(config),返回已設定好 baseURL、攔截器的實例,讓其他模組只需要呼叫 ApiClient.get('/users')
  5. 程式碼生成器或 DSL

    • 利用 class Builder 內的鏈式方法(return this)建立複雜物件,最終透過 build() 產出結果,類似 React 的 JSX 轉換流程。

總結

ES6+ 的 class 為 JavaScript 帶來了更清晰的物件導向語法,讓開發者可以:

  • 以類別的概念 抽象出共通行為與屬性。
  • 透過 extends / super 建立層次分明的繼承結構,重用與擴充程式碼。
  • 使用 getter / setter、static,提升封裝性與工具函式的可達性。
  • 結合私有欄位 #、mixins,在保持可讀性的同時避免過度繼承所帶來的複雜度。

在實務開發中,適度運用 class 與繼承可以讓 程式碼更具可維護性、可測試性,同時也符合現代前端/後端框架的設計哲學。當然,不要盲目追求繼承,根據需求選擇組合或 mixin 才是長期維護的關鍵。

掌握以上概念與最佳實踐後,你就能在專案中自如地使用 class,寫出結構良好、易於擴充的 JavaScript 程式碼。祝你開發順利,持續探索更進階的 ESNext 功能!