可變狀態的問題
const user = { name: "Alice", scores: [90, 85, 92] };
function addBonus(u) {
u.scores.push(100); // 直接修改傳進來的物件
return u;
}
addBonus(user);
console.log(user.scores); // [90, 85, 92, 100] ← 外部也變了!你把 user 傳給一個函式,你不知道那個函式有沒有修改它。如果程式複雜了,有十個函式都可能修改 user,追蹤 user.scores 什麼時候被改是噩夢。
Immutability 的原則:不改已有的,建立新的。
function addBonus(u) {
return { ...u, scores: [...u.scores, 100] }; // 建立新物件
}
const newUser = addBonus(user);
console.log(user.scores); // [90, 85, 92] ← 原本的不變
console.log(newUser.scores); // [90, 85, 92, 100]Immutability 的好處
1. 並發安全(Concurrency Safe)
如果資料不可變,多個 thread 同時讀取不需要鎖——不可能有 race condition(沒有人在寫,讀再多次都安全)。Erlang / Haskell / Rust 用 immutability 作為並發安全的基礎。
2. 容易推理(Easier Reasoning)
看到一個 immutable 物件,你知道它從建立後就不會改變。函式的輸出只取決於輸入,不取決於「某個全域狀態在某時間點是什麼值」。
3. 時間旅行(Time Travel)
Redux 的 time-travel debugging、React 的 shouldComponentUpdate——都是因為 immutable state 讓「比較前後的差異」變得容易(淺比較就夠了,prev !== next)。
各語言的 Immutability
JavaScript / TypeScript:
// const 讓變數不能重新賦值,但不讓物件本身不可變
const arr = [1, 2, 3];
arr.push(4); // OK,arr 本身還是同一個陣列
// Object.freeze 讓物件不可修改(淺層)
const obj = Object.freeze({ x: 1, y: { z: 2 } });
obj.x = 10; // 靜默失敗(strict mode 下報錯)
obj.y.z = 20; // 還是可以改,因為 freeze 只有一層
// Immutable.js / Immer:深層 immutable 工具Python:
# tuple 不可變,list 可變
point = (1, 2) # immutable
from dataclasses import dataclass
@dataclass(frozen=True)
class Point:
x: float
y: float
p = Point(1.0, 2.0)
p.x = 3.0 # FrozenInstanceErrorRust:預設不可變,需要 mut 關鍵字才能修改——Immutability 是語言的預設值。
Immutability 的代價
每次修改都建立新物件,記憶體使用量增加。對大型資料結構(百萬個 element 的陣列),每次操作都深拷貝會很昂貴。
解法:Persistent Data Structure(結構共享)——新物件和舊物件共享未被修改的部分,只複製改動的部分。Clojure 的資料結構、Immer.js、Java 的 PersistentHashMap 都是這個實作。
和 DDD Value Object 的關係
DDD 的 Value Object(見 C04)天然就是 Immutable 的——Money(100, "USD") 和 Money(100, "USD") 是相等的,不是同一個物件,而且 Money 的值一旦建立就不應該被修改(要「加錢」就建一個新的 Money)。Immutability 是 Value Object 設計的核心約束。