Class-Based & Prototype-Based

Class-Based vs Prototype-Based
TL;DROOP 兩大流派:Class-based(Java / C++ / C#)有「類」跟「實例」清楚二分;Prototype-based(JavaScript)沒有真正的類,所有物件都是「某個原型物件的複製 / 連結」。現代 JS 的 class 語法是 prototype 之上的糖衣,內部仍是 prototype。
主要參考MDN — Details of the Object Model
兩種流派的本質差別
| 維度 | Class-Based | Prototype-Based |
|---|---|---|
| 代表語言 | Java / C++ / C# / Python | JavaScript / 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);
特性:
-
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);
特性:
- 物件可以 隨時加 / 刪 / 改 屬性
- 沒有「類定義」這個概念,只有物件之間的 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);
這只是糖衣class 在 JS 內部仍然是 prototype-based。Employee 是一個 function,giveRaise 被掛在 Employee.prototype 上。用
Object.getPrototypeOf(v) === Employee.prototype驗證。ES6 加
class是為了讓 Java / C# 過來的開發者 寫起來自然,但本質沒變。
概念對照(各自怎麼說)
類 vs 物件 / 實例
| Class-based | Prototype-based |
|---|---|
| Class(類) | 沒對等概念 — 用 prototype object 替代 |
| Instance(實例) | Object(物件) |
new ClassName() | Object.create(proto) 或 new Constructor() |
繼承
// Java
class Manager extends Employee { ... }
// JS prototype chain
const managerProto = Object.create(employeePrototype);
managerProto.fireEmployee = function (e) { /* ... */ };
const m = Object.create(managerProto);
// JS class syntax(糖衣)
class Manager extends Employee { ... }
介面(Interface)
| Class-based | Prototype-based |
|---|---|
| 有 interface 關鍵字(Java / C# / TS) | 沒有 interface(只有 duck typing 跟 TypeScript 的 interface) |
Duck typing:「走起來像鴨子、叫起來像鴨子,那就是鴨子」 — JS 不檢查型別,只看物件 有沒有需要的方法。
function quack(duck) {
duck.quack(); // 不管 duck 是什麼類型,只要有 quack() 就 OK
}
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; // 區域變數
}
}
重點:
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 檔名規則(easy to forget)
// Account.java(檔名跟 public class 同名)
public class Account {
// 實作
}
// 一個檔案 ==只能有一個 public class==
class SomeOtherClass {
// OK,這個不是 public
}
規則:
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 的 interface 是 structural — 看 結構符不符合,不看 是否 explicitly implements。比 Java 的 nominal typing 彈性。