學習筆記——immutable

聰明絕頂的你與即將禿頭的我發表於2020-12-31

為什麼有immutable

JavaScript 中的物件一般是可變的(Mutable),因為使用了引用賦值,新的物件簡單的引用了原始物件,改變新的物件將影響到原始物件。

當應用複雜後,這樣會造成很大隱患,於是有了深拷貝,淺拷貝。但是這些操作會造成CPU和記憶體的浪費,於是就有了immutable。

什麼是immutable資料?

Immutable Data 就是一旦建立,就不能再被更改的資料。對 Immutable 物件的任何修改或新增刪除操作都會返回一個新的 Immutable 物件。

Immutable 實現的原理是 Persistent Data Structure(持久化資料結構),也就是使用舊資料建立新資料時,要保證舊資料同時可用且不變。同時為了避免 deepCopy 把所有節點都複製一遍帶來的效能損耗,Immutable 使用了 Structural Sharing(結構共享),即如果物件樹中一個節點發生變化,只修改這個節點和受它影響的父節點,其它節點則進行共享。

immutable的優點

1.降低了mutable帶來的複雜度。可變資料耦合了time和Value的概念,造成資料很難被回溯。

2.節省記憶體。immutable.js使用了結構共享來儘可能地複用記憶體,甚至以前使用的物件也可以再次被複用。沒有被引用的物件會被垃圾回收。

import { Map} from 'immutable';
let a = Map({
  select: 'users',
  filter: Map({ name: 'Cam' })
})
let b = a.set('select', 'people');

a === b; // false
a.get('filter') === b.get('filter'); // true

上面 a 和 b 共享了沒有變化的 filter 節點。

3.想回退到哪裡就拿出對應資料即可,很容易開發出撤銷重做這種功能。

4.併發安全。使用了 Immutable 之後,資料天生是不可變的,併發鎖就不需要了

5.函數語言程式設計。純函數語言程式設計比物件導向更適用於前端開發。因為只要輸入一致,輸出必然一致,這樣開發的元件更易於除錯和組裝。

 

immutable的缺點:

1.需要學習新的API

2.增加了資原始檔大小

3.容易與原生物件混淆

 

更多認識

immutable.is

2個immutable物件可以使用===進行比較,這樣是直接比較記憶體地址。但即使兩個物件的值是一樣的,也返回false。

let map1 = Immutable.Map({a:1, b:1, c:1});
let map2 = Immutable.Map({a:1, b:1, c:1});
map1 === map2;     //false

為了直接比較物件的值,immutable.js 提供了 Immutable.is 來做『值比較』,結果如下:

Immutable.is(map1, map2);  // true

Immutable.is 比較的是兩個物件的 hashCode 或 valueOf(對於 JavaScript 物件)。由於 immutable 內部使用了 Trie 資料結構來儲存,只要兩個物件的 hashCode 相等,值就是一樣的。這樣的演算法避免了深度遍歷比較,效能非常好。

cursor

由於 Immutable 資料一般巢狀非常深,為了便於訪問深層資料,Cursor 提供了可以直接訪問這個深層資料的引用。

import Immutable from 'immutable';
import Cursor from 'immutable/contrib/cursor';

let data = Immutable.fromJS({ a: { b: { c: 1 } } });
// 讓 cursor 指向 { c: 1 }
let cursor = Cursor.from(data, ['a', 'b'], newData => {
  // 當 cursor 或其子 cursor 執行 update 時呼叫
  console.log(newData);
});

cursor.get('c'); // 1
cursor = cursor.update('c', x => x + 1);
cursor.get('c'); // 2

 

實踐

React 做效能優化時有一個避免重複渲染的大招,就是使用 shouldComponentUpdate(),但它預設返回 true,即始終會執行 render() 方法,然後做 Virtual DOM 比較,並得出是否需要做真實 DOM 更新,這裡往往會帶來很多無必要的渲染併成為效能瓶頸。

我們也可以在 shouldComponentUpdate() 中使用使用 deepCopy 和 deepCompare 來避免無必要的 render(),但 deepCopy 和 deepCompare 一般都是非常耗效能的

Immutable 則提供了簡潔高效的判斷資料是否變化的方法,只需 === 和 is 比較就能知道是否需要執行 render(),而這個操作幾乎 0 成本,所以可以極大提高效能。修改後的 shouldComponentUpdate 是這樣的:

import { is } from 'immutable';

shouldComponentUpdate: (nextProps = {}, nextState = {}) => {
  const thisProps = this.props || {}, thisState = this.state || {};

  if (Object.keys(thisProps).length !== Object.keys(nextProps).length ||
      Object.keys(thisState).length !== Object.keys(nextState).length) {
    return true;
  }

  for (const key in nextProps) {
    if (!is(thisProps[key], nextProps[key])) {
      return true;
    }
  }

  for (const key in nextState) {
    if (thisState[key] !== nextState[key] && !is(thisState[key], nextState[key])) {
      return true;
    }
  }
  return false;
}