cover

結論先講

JavaScript 沒有真的 class,ES6 的 class 關鍵字是 prototype 的語法糖

真正的運作是 prototype chain:每個物件有個隱藏的 __proto__ 指標,指向它的「原型」。取屬性時沿著 chain 往上找,直到找到或到 null

const arr = [1, 2];
arr.map(...);  // Array.prototype.map
// arr → Array.prototype → Object.prototype → null

Class 語法讓 code 看起來像 Java,但底層機制完全不同。搞懂 prototype chain 才能解釋:

  • 為什麼 instance.method() 能跑
  • 為什麼繼承實際是「連 prototype 成鏈」
  • 為什麼 React 社群現在偏好 function component 而非 class component

Prototype Chain:物件的家族樹

const obj = { name: 'A' };
console.log(obj.__proto__);  // Object.prototype
console.log(obj.toString());  // 從 Object.prototype 繼承而來

每個物件都有 __proto__(現代 API:Object.getPrototypeOf(obj))。

鏈怎麼查

const arr = [1, 2, 3];
// arr.__proto__ === Array.prototype
// Array.prototype.__proto__ === Object.prototype
// Object.prototype.__proto__ === null

arr.map(...) 的流程:

  1. arr 自己有 map?沒有
  2. Array.prototypemap!用它
  3. arrthis 呼叫該 map

這就是「方法繼承」的本質——鏈式查找


class 是什麼

class User {
  constructor(name) {
    this.name = name;
  }
 
  greet() {
    return `Hi, ${this.name}`;
  }
}
 
const u = new User('Alice');
u.greet();  // "Hi, Alice"

看起來像 Java。實際上 JS 做了這些事

function User(name) {
  this.name = name;
}
 
User.prototype.greet = function() {
  return `Hi, ${this.name}`;
};
 
const u = new User('Alice');
// u.__proto__ === User.prototype

class 只是包裝 constructor function + 在 prototype 上加方法

驗證

typeof class Foo {}     // 'function'
class Foo {}.prototype  // { constructor: class Foo }

Class 就是 function。


new 做了什麼

const u = new User('Alice');

等同:

const u = {};                    // 1. 建立空物件
Object.setPrototypeOf(u, User.prototype);  // 2. 連上原型鏈
User.call(u, 'Alice');           // 3. 用 u 當 this 執行 constructor
// 4. 回傳 u(constructor 沒明確 return 時)

所以 u 能用 User.prototype.greet 因為鏈連上了。


繼承:連兩條鏈

class Admin extends User {
  constructor(name, role) {
    super(name);
    this.role = role;
  }
 
  whoAmI() {
    return `${this.name} (${this.role})`;
  }
}
 
const a = new Admin('Bob', 'superuser');
a.greet();     // 從 User.prototype 繼承
a.whoAmI();    // Admin.prototype 自己的

Prototype 鏈:

a.__proto__ === Admin.prototype
Admin.prototype.__proto__ === User.prototype
User.prototype.__proto__ === Object.prototype

a.greet() 查找過程:

  1. a 沒有 greet
  2. Admin.prototype 沒有
  3. User.prototype 有!用它

super 做什麼

super() 呼叫父類的 constructor,super.method() 呼叫父類的方法。

class Admin extends User {
  greet() {
    return super.greet() + ' (admin)';  // 呼叫 User.prototype.greet
  }
}

靜態方法 vs 實例方法

class MathUtils {
  static square(n) { return n * n; }    // 靜態:class 本身的方法
  cube(n) { return n * n * n; }          // 實例:prototype 上
}
 
MathUtils.square(5);                    // 25 ✅
new MathUtils().cube(5);                // 125 ✅
MathUtils.cube(5);                      // ❌ TypeError
new MathUtils().square(5);              // ❌ TypeError

在哪裡?

  • static 方法 → 直接掛在 class 本身MathUtils.square
  • 實例方法 → 掛在 MathUtils.prototype.cube

實戰:工具函式用 static(不用每次 new);需要 state 的邏輯用實例。


Private Field(現代)

傳統 JS 沒 private,大家用命名慣例:

class User {
  constructor() {
    this._password = '...';  // 底線表示「請不要碰」
  }
}

現代 JS(ES2022)有真的 private field

class User {
  #password;  // 真 private
 
  constructor(pw) {
    this.#password = pw;
  }
 
  check(pw) {
    return this.#password === pw;
  }
}
 
const u = new User('secret');
u.#password;  // ❌ SyntaxError(外部存取不到)

比 TypeScript 的 private 強——TS 只是編譯期檢查,runtime 仍可存取。# 是 runtime 保護。


為什麼 React 拋棄 class 轉 function

// 老 class 寫法
class Counter extends React.Component {
  state = { count: 0 };
  increment = () => this.setState({ count: this.state.count + 1 });
  render() { return <button onClick={this.increment}>{this.state.count}</button>; }
}
 
// 新 function 寫法
function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}

Function 為什麼贏

  1. 沒有 this 綁定問題(閉包直接抓變數)
  2. Code 更短(不用 constructor、bind、lifecycle 方法)
  3. Hooks 更強(useEffect、useMemo、useContext 組合力強)
  4. HMR 友善(function 重新載入比 class 可靠)
  5. TypeScript 推斷更好

2026 年,React / Vue 3 / Solid 都主推 function-based component。class 只在讀老 code 接觸。

但你仍要懂 prototype — 因為 JS 內建型別(Array、Map、Set、Date)都用 prototype,你寫 array.filter(...) 就在用 prototype chain。


實戰:什麼時候還會用 class

雖然 component 不用了,這些場景仍適合 class:

  1. Model / Entity 定義(DDD 專案)
  2. Error 自訂class ValidationError extends Error
  3. 大型 state 機器 / service(有多個相關方法 + 狀態)
  4. Web Componentsclass Foo extends HTMLElement
  5. 工具 class(例如 class Cache { ... }

不要強求。很多 class 都能用 factory function + closure 實作,code 更短、更好測。


Factory Function:Class 的替代方案

// Class
class Counter {
  constructor() { this.count = 0; }
  increment() { this.count++; }
  get() { return this.count; }
}
 
// Factory function(更簡潔、沒 this 問題)
function createCounter() {
  let count = 0;
  return {
    increment: () => count++,
    get: () => count,
  };
}
 
const c = createCounter();
c.increment();
c.get();  // 1

Factory 優點:

  • count 真 private(closure 保護)
  • this 綁定問題
  • 可以 TypeScript 推斷介面

缺點:

  • 每個 instance 方法都是新函式(class 只有一份在 prototype)
  • 大量 instance 時記憶體較高

實戰:少量 instance 用 factory,大量 instance(10k+)才考慮 class


實戰 Checklist

  • 懂 prototype chain 是查找機制,不是繼承的「複製」
  • class 是 syntactic sugar,底層仍是 prototype
  • React / Vue 3 優先用 function component,class 只讀老 code
  • 自訂 Error、Web Components 仍用 class
  • 少量 instance 考慮 factory function(更簡潔)
  • Private 用 #field(真 runtime 保護)
  • super() 要在 derived constructor 裡先呼叫才能用 this

相關文章