Immutability 為何重要?淺談 immutable.js 和 seamless-immutable

這陣子為了使用 react.js 重構專案,看了不少相關資料,來整理一下 immutable.js 的部份吧。以下的內容包含 immutablility、immutable.js 和 seamless-immutable。

Mutable

JavaScript 在創建變數、賦值後是可變的(mutable),且在賦值後,除了基本型態外,物件是使用 call by reference 的方式共享資料來源,即 。例如:

var obj = {
  a: 1,
  b: 2
};

var obj1 = obj;

若設定

obj1.a = 999;

obj.a //999

改變了 obj1.a 的值,同時也會更改到 obj.a的值。這樣共享的好處就是節省記憶體,壞處就是稍不注意會導致「改 A 壞 B 」的棘手問題。

一般的解法就是使用「深拷貝」(deep copy)而非淺拷貝(shallow copy),讓共享變成個別擁有自己的資料來源,缺點就是浪費記憶體。

那就讓 JavaScript 的資料結構變成 immutable 來解決。

Immutable

相較於 mutable,immutable 就是指在創建變數、賦值後便不可改變,若對其有任何變更(例如:新增、修改、刪除),就會回傳一個新值。

目前實作 immutablity 的 library 中較著名的就是 immutable.js 和 seamless-immutable,由於底層實作方式不同,分別有各自的專長。

immutable.js

immutable.js 的優點是

效能佳(perfermance)

immutable.js 利用結構共享(structural sharing)的方式實作 persistent data structure。 所有的更新都會產生新值,但內部利用結構分享來大大減低記憶體的使用。 例如:假設要新加入一個值到一個長度為 1000 的陣列,它並非產生一個新的長度為 1001 的陣列,而是少部份的物件被建立- 更改的部份新建立節點,而沒有更改的部份仍維持共享。

簡化變異的追蹤(mutation tracking)- Reference & Value Equality Check

在比對兩個物件是否相等時,不使用指標的比對(reference equality check),而是值的比對(value equality check)-減少了 reference equality check 所帶來的 recursive scan,因此效能較佳。

但由於 immutable.js 在實作上是把 JavaScript 物件外包一層糖衣做處理,這個產生的物件是 immutable 物件而非一般的 JavaScript 物件,因此若其它 library 是使用一般的 JavaScript 物件,交互使用上可能會產生一些困擾。

解法是使用另外的靜態類型檢查工具 / 系統(例如:TypeScript or Flow)或隱藏實作細節(例如:Redux)來處理。

seamless-immutable

另一個實作 immutablity 的方法,比起來是較為輕巧,和其它 JavaScript lib 的交互使用也很良好。

由於 seamless-immutable 是使用原生的 JavaScript 物件,不產生專用的資料結構,因此較輕巧、也和 JavaScript 的其他lib互動良好,但缺點就是使用 shallow copy 的方式產生新值來回傳,沒有 structural sharing 和 value equality check 所帶來的效能好處。

總結

Immutability 讓 JavaScript 降低了變異的複雜度,並且擁抱了現在正夯的函數編程(functional programming)。而實作 Immutability 特性的 library,目前最著名的就是 immutable.js 和 seamless-immutable。如果要選一種來使用,大概可以用這樣的方式決定:希望功能完善,但不介意整合或相容(與其它 library 的互動)、輕巧與否議題,可選 immutable.js;反之,若在意相容問題,或在乎輕巧,而不在意功能完善,那就選 seamless-immutable。

參考資料/推薦閱讀


immutable seamless-immutable react.js immutable.js