Mutable 物件
在JavaScript 中,物件是引用類型的數據,其優點在於頻繁的修改對象時都是在原對象的基礎上修改,並不需要重新創建,這樣可以有效的利用內存,不會造成內存空間的浪費,對象的這種特性可以稱為Mutable,中文的字面意思是「可變」。
對於Mutable 的對象,其靈活多變的優點有時可能會成為其缺點,越是靈活多變的資料越是不好控制,對於一個複雜結構的對象來說,一不小心就在某個不經意間修改了數據,假如該物件又在多個作用域中用到,此時很難預見數據是否改變以及何時改變的。
var obj = { /* 一个复杂结构的对象 */ }; doSomething(obj); // 上面的函数之行完后,此时的 obj 还是最初的那个 obj 吗?
針對這種問題,常規的解決方法可以透過將對象進行深拷貝的形式複製出一個新的對象,再在新對像上做修改的操作,這樣能確保數據的可控性,但是頻繁的複製會造成記憶體空間的大量浪費。
var obj = { /* 一个复杂结构的对象 */ }; // copy 出一个新的 obj2 // 但是 copy 操作会浪费内存空间 var obj2 = deepClone(obj); doSomething(obj2); // 上面的函数之行完后,无论 obj2 是否变化,obj 肯定还是原来那个 obj
Immutable 物件
為了能更好的解決上述的問題,出現了 Immutable 對象,Immutable 從字面上翻譯成中文是「不可變」。每次修改一個 Immutable 物件時都會建立一個新的不可變的對象,在新物件上操作並不會影響到原對象的資料。這個特殊的物件並不是JavaScript 新出的功能特性,而是業界為了解決這種問題所提供的一套解決方案,並且湧現出了一些優秀的開源類別庫,其中最有名的就是Facebook 的Lee Byron 開源的immutable.js。當然,Immutable 的這種解決方案並不是獨創的,而是來自於 Clojure 和 Scala。
Mutable 和 Immutable 的效能比較
對於 Mutable 的物件的低效率操作主要體現在複製和比較上,而 Immutable 物件就是解決了這兩大低效的痛點。
普通的Mutable 物件的深拷貝操作會將一整份資料都複製一遍,而Immutable 物件在修改資料時並不會複製一整份數據,而是將變化的節點與未變更的節點的父子關係轉移到一個新節點上,類似鍊錶的結構。從“複製” 的角度來看,做到了最小化的複製,未變化的部分都是共享的,Mutable 在複製的時候是“全量”,而Immutable 複製的是“增量”,對於內存空間的使用率的比較高低立判。
並且基於每次修改一個 Immutable 物件都會建立一個新的 Immutable 物件的這種特性可以將資料的修改狀態保存成一組快照,這也是挺方便的。
再來說說比較操作。對於 Mutable 的對象,如果要比較兩個物件是否相等,必須遍歷物件的每個節點進行比較,對於結構複雜的物件來說,其效率肯定高不到哪裡去。對於 Immutable 對象,immutable.js 提供了直接判斷兩個 Immutable 物件的「值」是否相等的 API。
var map1 = Immutable.Map({a:1, b:1, c:1}); var map2 = Immutable.Map({a:1, b:1, c:1}); assert(map1 !== map2); // 不同的 Immutable 实例,此时比较的是引用地址 assert(Immutable.is(map1, map2)); // map1 和 map2 的值相等,比较的是值 assert(map1.equals(map2)); // 与 Immutable.is 的作用一样
在實際的開發應用中,效能並不總是最關鍵和重要的,對於普通的JavaScript 的專案來說,由於Immutable 的特性帶來的資料的可控性比起效能來說更有優勢,對於Mutable 物件適合在封閉的作用域小範圍使用,而Immutable 物件適合資料需要跨多個作用域傳遞時使用。
Mutable 和 Immutable 在使用上的差異
immutable.js 提供了多種 Immutable 的資料結構:包含了 List Stack Map OrderedMap Set OrderedSet Record,這些資料結構與原生的 Mutable 的資料結構大致對應。
各資料結構的用法這裡不細說,主要說說 Immutable 物件與 Mutable 物件在使用上的差異。
原生的 Mutable 物件在「讀取」和「寫」上非常方便。
var mutableObj = {}; // 写入数据 mutableObj.foo = 'bar'; // 读取数据 console.log(mutableObj.foo);
而 Immutable 物件需要透過 set 和 get 來對資料進行「讀取」和「寫入」。
var immutableObj1 = Immutable.Map(); // 写入数据 var immutableObj2 = immutableObj1.set('foo', 'bar'); // 读取数据 console.log(immutableObj2.get('foo')); // => 'bar'
上面的例子為了說明 set 方法的使用才在一開始創建了一個空對象,實際上可以在實例化的時候傳初始值。
var immutableObj = Immutable.Map({'foo', 'bar'});
對於層級比較深的數據,immutable.js 提供的存取介面很方便。
var immutableObj1 = Immutable.fromJS({ a: { b: 'c' }, d: [1, 2, 3] }); // 读取深层级的数据 console.log(immutableObj1.getIn(['a', 'b'])); // => 'c' console.log(immutableObj1.getIn(['d', 1])); // => 2 // 修改深层级的数据 var immutableObj2 = immutableObj1.setIn(['a', 'b'], 'd'); console.log(immutableObj2.getIn(['a', 'b'])); // => 'd'
如果是原生的 Mutable 對象,在鍊式存取一個深層的資料時可能會報物件 undefined 的錯誤,而 Immutable 物件在碰到這種情況時不會報錯,回傳的是 undefined。
在調試的時候,如果想查看一個 Immutable 物件的內部結構,建議使用 toJSON() 先轉換為普通的 Mutable 物件。