Immutable.js
Much of what makes application development difficult is tracking mutation and maintaining state. Developing with immutable data encourages you to think differently about how data flows through your application.
「对 Immutable 对象的任何修改或添加删除操作都会返回一个新的 Immutable 对象」。
These data structures are highly efficient on modern JavaScript VMs by using structural sharing via hash maps tries and vector tries as popularized by Clojure and Scala, minimizing the need to copy or cache data.
Immutable 实现的原理是 Persistent Data Structure(持久化数据结构),也就是使用旧数据创建新数据时,要保证旧数据同时可用且不变。同时为了避免 deepCopy 把所有节点都复制一遍带来的性能损耗,Immutable 使用了 Structural Sharing(结构共享),即如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其它节点则进行共享。
特点:
- 降低了 Mutable 所带来的复杂度。
- 使用了 Structure Sharing 而尽量复用内存,无引用的对象会被垃圾回收。
- 时间旅行。
- 并发安全,并不需要并发锁这种东西的存在了。
- 拥抱 Functional Programming。
代价:
- 新的 API 学习成本、增加资源文件大小,容易和原生对象混淆。
// Create
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
// Nested Structures
var nested = Immutable.fromJS({a:{b:{c:[3,4,5]}}});
// Map { a: Map { b: Map { c: List [ 3, 4, 5 ] } } }
var nested2 = nested.mergeDeep({a:{b:{c:[3,4,5]}}});
// 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 } } }
// Compare
var map1 = Immutable.Map({a:1, b:2, c:3});
var map2 = map1.set('b', 2);
assert(map1.equals(map2) === true);
var map1 = Immutable.Map({a:1, b:1, c:1});
var map2 = Immutable.Map({a:1, b:1, c:1});
assert(map1 !== map2);
assert(Object.is(map1, map2) === false);
assert(Immutable.is(map1, map2) === true);
// Merge
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 }
// Convert Back
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]}'
// Helpers
Immutable.Range(10, 30, 5); // [10, 15, 20, 25, 30]
Immutable.Repeat('bar',4); // ['bar','bar','bar','bar']
List, Set and Ordered Set
- Set: A Collection of unique values with O(log32 N) adds and has.
List.isList()
List.of(values)
Set.fromKeys(object)
list.set(index, value)
list.delete(index)
list.insert(index, value)
list.clear()
list.push(v)
list.pop()
list.shift()
list.unshift(v)
list.update(index, updater)
list.merge() / mergeWith() / mergeDeep() / mergeDeepWith()
list.setSize(size)
list.setIn(path, value)
list.deleteIn(path)
list.updateIn()
list.mergeIn()
list.mergeDeepIn()
set.add()
set.union()
set.intersect()
set.subtract()
Map, Ordered Map
They almost share the same API.
- Ordered Map: A type of Map that has the additional guarantee that the iteration order of entries will be the order in which they were set().
Map().set(List.of(1), 'listofone').get(List.of(1));
// 'listofone'
Map.isMap()
Map.of()
Map.set() | delete() | clear() | update() | merge()
var x = Immutable.Map({a: 10, b: 20, c: 30});
var y = Immutable.Map({b: 40, a: 50, d: 60});
x.mergeWith((prev, next) => prev / next, y) // { a: 0.2, b: 0.5, c: 30, d: 60 }
y.mergeWith((prev, next) => prev / next, x) // { b: 2, a: 5, d: 60, c: 30 }
var x = Immutable.fromJS({a: { x: 10, y: 10 }, b: { x: 20, y: 50 } });
var y = Immutable.fromJS({a: { x: 2 }, b: { y: 5 }, c: { z: 3 } });
x.mergeDeepWith((prev, next) => prev / next, y)
// {a: { x: 5, y: 10 }, b: { x: 20, y: 10 }, c: { z: 3 } }
map.setIn()
map.deleteIn()
map.updateIn()
map.mergeIn()
map.mergeDeepIn()
// Unique Features
Stack
Stacks are indexed collections which support very efficient O(1) addition and removal from the front using unshift(v) and shift().
Stack.isStack()
Stack.of()
// Read Values
stack.peek() === stack.first()
stack.clear()
stack.push()
stack.pop()
Record
Creates a new Class which produces Record instances
. A record is similar to a JS object, but enforce a specific set of allowed string keys, and have default values.
var ABRecord = Record({a:1, b:2})
var myRecord = new ABRecord({b:3})
myRecord.size // 2
myRecord.get('a') // 1
myRecord.get('b') // 3
myRecordWithoutB = myRecord.remove('b')
myRecordWithoutB.get('b') // 2
myRecordWithoutB.size // 2
var myRecord = new ABRecord({b:3, x:10})
myRecord.get('x') // undefined
class ABRecord extends Record({a:1,b:2}) {
getAB() {
return this.a + this.b;
}
}
var myRecord = new ABRecord({b: 3})
myRecord.getAB() // 4
Seq and Iterable
Represents a sequence of values, but may not be backed by a concrete data structure.
Seq describes a lazy operation, allowing them to efficiently chain use of all the Iterable methods (such as map and filter).
- Seq is immutable — Once a Seq is created, it cannot be changed, appended to, rearranged or otherwise modified. Instead, any mutative method called on a Seq will return a new Seq.
- Seq is lazy — Seq does as little work as necessary to respond to any method call. Values are often created during iteration, including implicit iteration when reducing or converting to a concrete data structure such as a List or JavaScript Array.
Seq allows for the efficient chaining of operations, allowing for the expression of logic that can otherwise be very tedious.
var oddSquares = Immutable.Seq.of(1,2,3,4,5,6,7,8)
.filter(x => x % 2).map(x => x * x);
// filter only called thirds and map only called once
Basic APIs
Iterable.isIterable()
Iterable.isKeyed()
Iterable.isIndexed()
Iterable.isAssociative()
Iterable.isOrdered()
Seq.isSeq()
Seq.of()
Seq.size
// map will execute only 3 times
var squares = Seq.of(1,2,3).map(x => x * x).cacheResult();
squares.join() + squares.join();
seq.get() | set() | has() | includes() | first() | last();
seq.getIn() | hasIn()
seq.toJS() | toArray() | toObject()
seq.toMap() | toOrderedMap() | toSet() | toOrderedSet()
seq.toList() | toStack()
// Iterators
seq.keys()
seq.values()
seq.entries()
// Iterables Seq
seq.keySeq()
seq.valueSeq()
seq.entrySeq()
// Seq Algorithms
seq.map()
seq.filter()
seq.filterNot()
seq.reverse()
seq.sort()
seq.sortBy()
seq.groupBy()
// Subsets
seq.slice(begin, end)
seq.rest()
seq.butLast()
seq.skip(excludeAfter)
seq.skipLast()
seq.skipWhile()
seq.skipUntil()
seq.take()
seq.takeLast()
seq.taskWhile()
seq.takeUntil()
// Combination
seq.concat()
seq.flatten()
seq.flatMap()
// Reducing the Values
seq.reduce()
seq.reduceRight()
seq.every()
seq.some()
seq.join()
seq.count()
seq.countBy()
seq.isEmpty()
// Search
seq.find()
seq.findLast()
seq.findEntry()
seq.findLastEntry()
seq.max()
seq.maxBy()
seq.min()
seq.minBy()
// Comparison
seq.isSubset()
seq.isSuperset()