【翻譯】Immutable

原文地址: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>

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容