跳到主要內容
← 回到學習

學習 · 程式語言

Class-Based & Prototype-Based

Class-Based & Prototype-Based 封面圖
目錄 · 10 個段落
  1. 01兩種流派的本質差別
  2. 02Class-Based(Java 範例)
  3. 03Prototype-Based(JavaScript)
  4. 04ES6 class 語法是什麼?
  5. 05概念對照(各自怎麼說)
  6. 06OOP 四大特性(兩種流派都支援)
  7. 07變數範圍(Java 為例)
  8. 08Java 檔名規則(easy to forget)
  9. 09為什麼 JS 走 prototype-based?
  10. 10現代 TypeScript 同時擁有兩者

Class-Based vs Prototype-Based

TL;DR
OOP 兩大流派:Class-based(Java / C++ / C#)有「類」跟「實例」清楚二分;Prototype-based(JavaScript)沒有真正的類,所有物件都是「某個原型物件的複製 / 連結」。現代 JS 的 class 語法是 prototype 之上的糖衣,內部仍是 prototype。
主要參考
MDN — Details of the Object Model

兩種流派的本質差別

維度Class-BasedPrototype-Based
代表語言Java / C++ / C# / PythonJavaScript / Self / Lua
物件的「模板」類(Class) — 抽象的類型原型(Prototype) — 是另一個具體物件
創建物件new Class()Object.create(proto)
繼承繼承類(extends)連結原型鏈(__proto__)
編譯期 / 執行期大多 編譯期(static)執行期(dynamic)

Class-Based(Java 範例)

類:抽象的「規格書」(blueprint),不是物件本身。 實例:依規格產生的具體 物件(memory 中的東西)。

// 類:規格書
public class Employee {
    private String name;
    private int salary;
 
    public Employee(String name, int salary) {
        this.name = name;
        this.salary = salary;
    }
 
    public void giveRaise(int amount) {
        this.salary += amount;
    }
}
 
// 實例:具體存在的人
Employee victoria = new Employee("Victoria", 50000);
victoria.giveRaise(5000);
java

特性:

  • Victoria 的屬性必定來自 Employee 類的定義
  • 不能臨時加一個 Employee 沒有的屬性(static type 語言會編譯失敗)
  • 結構嚴謹、好維護、IDE 補全強

Prototype-Based(JavaScript)

沒有真正的「類」。所有物件都是 某個其他物件的複製或連結。

// "Prototype" 是個 plain object
const employeePrototype = {
  giveRaise(amount) {
    this.salary += amount;
  },
};
 
// 從 prototype 建立物件
const victoria = Object.create(employeePrototype);
victoria.name = 'Victoria';
victoria.salary = 50000;
victoria.giveRaise(5000);
javascript

特性:

  • 物件可以 隨時加 / 刪 / 改 屬性
  • 沒有「類定義」這個概念,只有物件之間的 prototype chain
  • 屬性查找順序:自己 → __proto__ → __proto__.__proto__ → ... → null

ES6 class 語法是什麼?

class Employee {
  constructor(name, salary) {
    this.name = name;
    this.salary = salary;
  }
  giveRaise(amount) {
    this.salary += amount;
  }
}
 
const v = new Employee('Victoria', 50000);
javascript
這只是糖衣
class 在 JS 內部仍然是 prototype-based。Employee 是一個 function,giveRaise 被掛在 Employee.prototype 上。

用 Object.getPrototypeOf(v) === Employee.prototype 驗證。

ES6 加 class 是為了讓 Java / C# 過來的開發者 寫起來自然,但本質沒變。


概念對照(各自怎麼說)

類 vs 物件 / 實例

Class-basedPrototype-based
Class(類)沒對等概念 — 用 prototype object 替代
Instance(實例)Object(物件)
new ClassName()Object.create(proto) 或 new Constructor()

繼承

// Java
class Manager extends Employee { ... }
java
// JS prototype chain
const managerProto = Object.create(employeePrototype);
managerProto.fireEmployee = function (e) { /* ... */ };
const m = Object.create(managerProto);
 
// JS class syntax(糖衣)
class Manager extends Employee { ... }
javascript

介面(Interface)

Class-basedPrototype-based
有 interface 關鍵字(Java / C# / TS)沒有 interface(只有 duck typing 跟 TypeScript 的 interface)

Duck typing:「走起來像鴨子、叫起來像鴨子,那就是鴨子」 — JS 不檢查型別,只看物件 有沒有需要的方法。

function quack(duck) {
  duck.quack();   // 不管 duck 是什麼類型,只要有 quack() 就 OK
}
javascript

OOP 四大特性(兩種流派都支援)

特性中文兩種流派的實作
Abstraction抽象Class-based: abstract class / Prototype: 用 plain object
Encapsulation封裝private / public / TS / # 私有欄位
Inheritance繼承extends / prototype chain
Polymorphism多型兩者都支援(覆寫方法)

變數範圍(Java 為例)

public class Container {
    static int classVar = 0;        // 類變數(所有實例共享)
    int instanceVar = 0;            // 實例變數(每個實例獨立)
 
    void method() {
        int localVar = 0;           // 區域變數
    }
}
java

重點:

  • static 變數 是 全類唯一份,改一個實例會影響全部
  • instance 變數 是 每個物件各自一份
  • final 變數 是 不可變(常數)
class Test {
    static int iValue = 0;
}
 
public class Main {
    public static void main(String[] args) {
        Test t1 = new Test();
        Test t2 = new Test();
        System.out.println(t1.iValue);   // 0
        t2.iValue = 10;
        System.out.println(t1.iValue);   // ⚠️ 也變 10!(static 共享)
    }
}
java

Java 檔名規則(easy to forget)

// Account.java(檔名跟 public class 同名)
public class Account {
    // 實作
}
 
// 一個檔案 ==只能有一個 public class==
class SomeOtherClass {
    // OK,這個不是 public
}
java

規則:

  • public class 必須跟檔名同名,且每個檔案最多一個 public class
  • 不是 public 的 class 在 compile 後會生成獨立 .class 檔
  • 執行時用 class 名稱(不是 .java 檔名)

為什麼 JS 走 prototype-based?

設計者 Brendan Eich 1995 年寫 JS 時 受 Self / Scheme 影響:

  • 靈活:物件可隨時改造
  • 簡單:不需要 class 機制,只需要 object + reference
  • delegation:沒有的方法自動往上找

代價:

  • 入門者看到「沒有 class 」很困惑
  • 易出錯(隨手改 prototype 影響全 app)
  • ES6 加 class 糖衣 補救

現代 TypeScript 同時擁有兩者

// Class-based 風格(TS 強型別)
class Employee {
  constructor(public name: string, public salary: number) {}
  giveRaise(amount: number) { this.salary += amount; }
}
 
// 介面(structural typing,duck typing 強化版)
interface Quackable {
  quack(): void;
}
 
function makeNoise(thing: Quackable) {
  thing.quack();
}
 
// 任何有 quack() 的物件都行,不用 implements
makeNoise({ quack: () => console.log('quack!') });
typescript

TypeScript 的 interface 是 structural — 看 結構符不符合,不看 是否 explicitly implements。比 Java 的 nominal typing 彈性。

目錄