原文地址:http://facebook.github.io/immutable-js/
JavaScript的不可變數(shù)據(jù)集
Immutable一旦創(chuàng)建就不能被修改,可以使用軟件開發(fā)更簡(jiǎn)單,無(wú)副作用的復(fù)制,高級(jí)記憶,使用簡(jiǎn)單邏輯改變偵探技術(shù)。持久化數(shù)據(jù)提供了一個(gè)靈活的api,用以產(chǎn)生新數(shù)據(jù),而不是在對(duì)數(shù)據(jù)進(jìn)行改變。
Immutable.js 提供了很多持久化數(shù)據(jù)結(jié)構(gòu),包括List,Stack,Map,OrderMap,Set,OrderedSet 和 Record。
由于使用了hash maps tries 和 vector tries的結(jié)構(gòu)化分享機(jī)制,這些數(shù)據(jù)結(jié)構(gòu)高效運(yùn)行在現(xiàn)代JavaScript虛擬機(jī)里。
Immutable還提供了Seq,可以使用高效的集合鏈路方法,無(wú)須創(chuàng)建中間表現(xiàn)。
一、開始
使用npm安裝immutable
npm install immutable
然后在模塊中引用
var Immutable = require('immutable');
var map1 = Immutable.Map({a:1, b:2, c:3});
var map2 = map1.set('b', 50);
map1.get('b'); // 2
map2.get('b'); // 50
瀏覽器
下載immutable.min.js,然后通過Script標(biāo)簽引入:
<script src="immutable.min.js"></script>
<script>
var map1 = Immutable.Map({a:1, b:2, c:3});
var map2 = map1.set('b', 50);
map1.get('b'); // 2
map2.get('b'); // 50
</script>
或者通過AMD加載器(如RequireJS)引入:
require(['./immutable.min.js'], function (Immutable) {
var map1 = Immutable.Map({a:1, b:2, c:3});
var map2 = map1.set('b', 50);
map1.get('b'); // 2
map2.get('b'); // 50
});
二、關(guān)于數(shù)據(jù)持久化
應(yīng)用開發(fā)大部分的難點(diǎn)在于追蹤狀態(tài)的變化和維持。Immutable給你提供了不同的方法去思考數(shù)據(jù)在程序中的流動(dòng)。
在程序中訂閱數(shù)據(jù)變化的事件會(huì)帶來很大的開銷,進(jìn)而影響性能,甚至無(wú)法進(jìn)行正確的數(shù)據(jù)同步。由于Immutable數(shù)據(jù)不可變,所以拋棄了數(shù)據(jù)的訂閱機(jī)制。
Immutable的數(shù)據(jù)模型和React配合良好,尤其是使用了Flux思想的程序。
當(dāng)數(shù)據(jù)從上而下傳遞而不是通過訂閱時(shí),你只需要專注于處理當(dāng)前的邏輯。
Immutabe集合應(yīng)該被當(dāng)做values而不是objects. objects表示隨著時(shí)間推移可能發(fā)生變化的對(duì)象,而values表示某個(gè)時(shí)間下的對(duì)象的狀態(tài)。這點(diǎn)對(duì)于理解Immutable的正確使用非常關(guān)鍵。為了將Immutable 視作values, 請(qǐng)使用Immutable.is() 函數(shù)或者.equals()方法來判斷相等性,不應(yīng)該使用===操作符,因?yàn)?==通過引用來判斷一致性。
var map1 = Immutable.Map({a:1, b:2, c:3});
var map2 = map1.set('b', 2);
assert(map1.equals(map2) === true);
var map3 = map1.set('b', 50);
assert(map1.equals(map3) === false);
注意:出于性能優(yōu)化的考慮,當(dāng)一個(gè)操作產(chǎn)生相同的數(shù)據(jù)時(shí),Immutable返回已經(jīng)存在的數(shù)據(jù),也就是引用也相同,方便===判斷一致性。在Immutable的內(nèi)部實(shí)現(xiàn)中,其實(shí)有用到了===操作符。
對(duì)于Immutable對(duì)象,它也可以通過復(fù)制引用來被復(fù)制。因?yàn)閺?fù)制引用比復(fù)制整個(gè)對(duì)象來說,系統(tǒng)開銷要小得多。
var map1 = Immutable.Map({a:1, b:2, c:3});
var clone = map1;
三、Javascript API
受Clojure, Scala, Haskell 和其他函數(shù)式編程語(yǔ)言的影響,Immutable將這些思想注入了Javascript。它提供了面向?qū)ο蟮腁PI,類似于ES6的Array,Map和Set.
與傳統(tǒng)的js數(shù)組方法不同,像Immutable.js的push,set,unshift,splice方法和slice,concat方法總是會(huì)返回新的immutable數(shù)據(jù)。
var list1 = Immutable.List.of(1, 2);
var list2 = list1.push(3, 4, 5);
var list3 = list2.unshift(0);
var list4 = list1.concat(list2, list3);
assert(list1.size === 2);
assert(list2.size === 5);
assert(list3.size === 6);
assert(list4.size === 13);
assert(list4.get(0) === 1);
Immutable.js里,Array有的方法,Immutable.List里幾乎都有;Map有的方法,Immutable.Map里幾乎都有;Set有的方法,Immutable.Set里幾乎都有,包括遍歷操作方法foreach()和map()。
var alpha = Immutable.Map({a:1, b:2, c:3, d:4});
alpha.map((v, k) => k.toUpperCase()).join();
// 'A,B,C,D'
接收原生的javascript對(duì)象
Immutable可以接收原生的javascript Array和Object.
var map1 = Immutable.Map({a:1, b:2, c:3, d:4});
var map2 = Immutable.Map({c:10, a:20, t:30});
var obj = {d:100, o:200, g:300};
var map3 = map1.merge(map2, obj);
// Map { a: 20, b: 2, c: 10, d: 100, t: 30, o: 200, g: 300 }
Immutable可以把JS 的 Array或者Object看成是可迭代的。你可以充分利用這點(diǎn),對(duì)Object使用高級(jí)的集合方法。
因?yàn)镾eq的懶惰性,它不緩存任何中間結(jié)果,所以這些操作是很高效的。
var myObject = {a:1,b:2,c:3};
Immutable.Seq(myObject).map(x => x * x).toObject();
// { a: 1, b: 4, c: 9 }
記住,當(dāng)使用js對(duì)象來構(gòu)造Immutable Maps時(shí),js對(duì)象的屬性必須是字符串格式。
var obj = { 1: "one" };
Object.keys(obj); // [ "1" ]
obj["1"]; // "one"
obj[1]; // "one"
var map = Immutable.fromJS(obj);
map.get("1"); // "one"
map.get(1); // undefined
轉(zhuǎn)換為原生的javascript對(duì)象
所有可迭代的Immutable數(shù)據(jù)都可以通過toArray(),toObject或者toJs()轉(zhuǎn)換成原生的javascript Array和Object. 所有可迭代的Immutable數(shù)據(jù)都實(shí)現(xiàn)了toJSON()方法,可以直接被JSON.stringify使用。
var deep = Immutable.Map({ a: 1, b: 2, c: Immutable.List.of(3, 4, 5) });
deep.toObject() // { a: 1, b: 2, c: List [ 3, 4, 5 ] }
deep.toArray() // [ 1, 2, List [ 3, 4, 5 ] ]
deep.toJS() // { a: 1, b: 2, c: [ 3, 4, 5 ] }
JSON.stringify(deep) // '{"a":1,"b":2,"c":[3,4,5]}'
擁抱ES6
Immutable充分利用ES6的特性。文中所有代碼都是以ES6呈現(xiàn)的,如果需要在所有瀏覽器上運(yùn)行,請(qǐng)把它轉(zhuǎn)換成ES3.
// ES6
foo.map(x => x * x);
// ES3
foo.map(function (x) { return x * x; });
四、嵌套結(jié)構(gòu)
Immutable數(shù)據(jù)容易使用嵌套,多層的樹結(jié)構(gòu),類似JSON
var nested = Immutable.fromJS({a:{b:{c:[3,4,5]}}});
// Map { a: Map { b: Map { c: List [ 3, 4, 5 ] } } }
Immutable提供了一些非常有用的方法用以讀取和操作嵌套數(shù)據(jù)。最常用到的就是mergeDeep, getIn, setIn, and updateIn,由List,Map和OrderedMap提供。
var nested2 = nested.mergeDeep({a:{b:{d:6}}});
// Map { a: Map { b: Map { c: List [ 3, 4, 5 ], d: 6 } } }
nested2.getIn(['a', 'b', 'd']); // 6
var nested3 = nested2.updateIn(['a', 'b', 'd'], value => value + 1);
// Map { a: Map { b: Map { c: List [ 3, 4, 5 ], d: 7 } } }
var nested4 = nested3.updateIn(['a', 'b', 'c'], list => list.push(6));
// Map { a: Map { b: Map { c: List [ 3, 4, 5, 6 ], d: 7 } } }
五、懶惰的Seq
Seq是不可變的——一旦Seq被創(chuàng)建,就不能被更改。
Seq是懶惰的——對(duì)于方法調(diào)用,Seq盡可能的少做操作。
比如,下面這段代碼不做任何操作,因?yàn)镾eq沒有被使用:
var oddSquares = Immutable.Seq.of(1,2,3,4,5,6,7,8)
.filter(x => x % 2).map(x => x * x);
一旦Seq被使用,它就執(zhí)行必要的操作。下面例子中,沒有中間數(shù)組被創(chuàng)建,filter被調(diào)用 了3次,map只被調(diào)用了2次。
console.log(oddSquares.get(1));
通過.toSeq(),所有集合類型數(shù)據(jù)可以被轉(zhuǎn)換成Seq.
var seq = Immutable.Map({a:1, b:1, c:1}).toSeq();
Seq允許鏈?zhǔn)讲僮鳎?/p>
seq.flip().map(key => key.toUpperCase()).flip().toObject();
// { A: 1, B: 1, C: 1 }
表達(dá)邏輯亦如此:
Immutable.Range(1, Infinity)
.skip(1000)
.map(n => -n)
.filter(n => n % 2 === 0)
.take(2)
.reduce((r, n) => r * n, 1);
// 1006008
六、判斷相等
Immutable提供了純數(shù)據(jù)的相等性判斷(區(qū)別于引用判斷)
var map1 = Immutable.Map({a:1, b:1, c:1});
var map2 = Immutable.Map({a:1, b:1, c:1});
assert(map1 !== map2); // two different instances
assert(Immutable.is(map1, map2)); // have equivalent values
assert(map1.equals(map2)); // alternatively use the equals method
Immutable.is()使用了跟Object.is一樣的相等性判斷機(jī)制。
七、批量變化
如果在返回之前,需要做一系列的數(shù)據(jù)變化,Immutable提供了withMutations方法用以批量變化來提升性能。
var list1 = Immutable.List.of(1,2,3);
var list2 = list1.withMutations(function (list) {
list.push(4).push(5).push(6);
});
assert(list1.size === 3);
assert(list2.size === 6);
重要:只有set,push,pop等少部分的方法可以在withMutations方法里使用。因?yàn)檫@些方法可以直接用在持久化數(shù)據(jù)結(jié)構(gòu)上。而不像map,filter,sort和splice方法,會(huì)返回新的不可變數(shù)據(jù)結(jié)構(gòu),永遠(yuǎn)不會(huì)改變?cè)瓉淼淖兞俊?/p>