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 学习成本、增加资源文件大小,容易和原生对象混淆。
  1. // Create
  2. var Immutable = require('immutable');
  3. var map1 = Immutable.Map({a:1, b:2, c:3});
  4. var map2 = map1.set('b', 50);
  5. map1.get('b'); // 2
  6. map2.get('b'); // 50
  7. // Nested Structures
  8. var nested = Immutable.fromJS({a:{b:{c:[3,4,5]}}});
  9. // Map { a: Map { b: Map { c: List [ 3, 4, 5 ] } } }
  10. var nested2 = nested.mergeDeep({a:{b:{c:[3,4,5]}}});
  11. // Map { a: Map { b: Map { c: List [ 3, 4, 5 ], d: 6 } } }
  12. nested2.getIn(['a', 'b', 'd']); // 6
  13. var nested3 = nested2.updateIn(['a', 'b', 'd'], value => value + 1);
  14. // Map { a: Map { b: Map { c: List [ 3, 4, 5 ], d: 7 } } }
  15. var nested4 = nested3.updateIn(['a', 'b', 'c'], list => list.push(6));
  16. // Map { a: Map { b: Map { c: List [ 3, 4, 5, 6 ], d: 7 } } }
  17. // Compare
  18. var map1 = Immutable.Map({a:1, b:2, c:3});
  19. var map2 = map1.set('b', 2);
  20. assert(map1.equals(map2) === true);
  21. var map1 = Immutable.Map({a:1, b:1, c:1});
  22. var map2 = Immutable.Map({a:1, b:1, c:1});
  23. assert(map1 !== map2);
  24. assert(Object.is(map1, map2) === false);
  25. assert(Immutable.is(map1, map2) === true);
  26. // Merge
  27. var map1 = Immutable.Map({a:1, b:2, c:3, d:4});
  28. var map2 = Immutable.Map({c:10, a:20, t:30});
  29. var obj = {d:100, o:200, g:300};
  30. var map3 = map1.merge(map2, obj);
  31. // Map { a: 20, b: 2, c: 10, d: 100, t: 30, o: 200, g: 300 }
  32. // Convert Back
  33. var deep = Immutable.Map({ a: 1, b: 2, c: Immutable.List.of(3, 4, 5) });
  34. deep.toObject() // { a: 1, b: 2, c: List [ 3, 4, 5 ] }
  35. deep.toArray() // [ 1, 2, List [ 3, 4, 5 ] ]
  36. deep.toJS() // { a: 1, b: 2, c: [ 3, 4, 5 ] }
  37. JSON.stringify(deep) // '{"a":1,"b":2,"c":[3,4,5]}'
  38. // Helpers
  39. Immutable.Range(10, 30, 5); // [10, 15, 20, 25, 30]
  40. 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.
  1. List.isList()
  2. List.of(values)
  3. Set.fromKeys(object)
  4. list.set(index, value)
  5. list.delete(index)
  6. list.insert(index, value)
  7. list.clear()
  8. list.push(v)
  9. list.pop()
  10. list.shift()
  11. list.unshift(v)
  12. list.update(index, updater)
  13. list.merge() / mergeWith() / mergeDeep() / mergeDeepWith()
  14. list.setSize(size)
  15. list.setIn(path, value)
  16. list.deleteIn(path)
  17. list.updateIn()
  18. list.mergeIn()
  19. list.mergeDeepIn()
  20. set.add()
  21. set.union()
  22. set.intersect()
  23. 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().
  1. Map().set(List.of(1), 'listofone').get(List.of(1));
  2. // 'listofone'
  3. Map.isMap()
  4. Map.of()
  5. Map.set() | delete() | clear() | update() | merge()
  6. var x = Immutable.Map({a: 10, b: 20, c: 30});
  7. var y = Immutable.Map({b: 40, a: 50, d: 60});
  8. x.mergeWith((prev, next) => prev / next, y) // { a: 0.2, b: 0.5, c: 30, d: 60 }
  9. y.mergeWith((prev, next) => prev / next, x) // { b: 2, a: 5, d: 60, c: 30 }
  10. var x = Immutable.fromJS({a: { x: 10, y: 10 }, b: { x: 20, y: 50 } });
  11. var y = Immutable.fromJS({a: { x: 2 }, b: { y: 5 }, c: { z: 3 } });
  12. x.mergeDeepWith((prev, next) => prev / next, y)
  13. // {a: { x: 5, y: 10 }, b: { x: 20, y: 10 }, c: { z: 3 } }
  14. map.setIn()
  15. map.deleteIn()
  16. map.updateIn()
  17. map.mergeIn()
  18. map.mergeDeepIn()
  19. // Unique Features

Stack

Stacks are indexed collections which support very efficient O(1) addition and removal from the front using unshift(v) and shift().

  1. Stack.isStack()
  2. Stack.of()
  3. // Read Values
  4. stack.peek() === stack.first()
  5. stack.clear()
  6. stack.push()
  7. 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.

  1. var ABRecord = Record({a:1, b:2})
  2. var myRecord = new ABRecord({b:3})
  3. myRecord.size // 2
  4. myRecord.get('a') // 1
  5. myRecord.get('b') // 3
  6. myRecordWithoutB = myRecord.remove('b')
  7. myRecordWithoutB.get('b') // 2
  8. myRecordWithoutB.size // 2
  9. var myRecord = new ABRecord({b:3, x:10})
  10. myRecord.get('x') // undefined
  11. class ABRecord extends Record({a:1,b:2}) {
  12. getAB() {
  13. return this.a + this.b;
  14. }
  15. }
  16. var myRecord = new ABRecord({b: 3})
  17. 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.

  1. var oddSquares = Immutable.Seq.of(1,2,3,4,5,6,7,8)
  2. .filter(x => x % 2).map(x => x * x);
  3. // filter only called thirds and map only called once

Basic APIs

  1. Iterable.isIterable()
  2. Iterable.isKeyed()
  3. Iterable.isIndexed()
  4. Iterable.isAssociative()
  5. Iterable.isOrdered()
  6. Seq.isSeq()
  7. Seq.of()
  8. Seq.size
  9. // map will execute only 3 times
  10. var squares = Seq.of(1,2,3).map(x => x * x).cacheResult();
  11. squares.join() + squares.join();
  12. seq.get() | set() | has() | includes() | first() | last();
  13. seq.getIn() | hasIn()
  14. seq.toJS() | toArray() | toObject()
  15. seq.toMap() | toOrderedMap() | toSet() | toOrderedSet()
  16. seq.toList() | toStack()
  17. // Iterators
  18. seq.keys()
  19. seq.values()
  20. seq.entries()
  21. // Iterables Seq
  22. seq.keySeq()
  23. seq.valueSeq()
  24. seq.entrySeq()
  25. // Seq Algorithms
  26. seq.map()
  27. seq.filter()
  28. seq.filterNot()
  29. seq.reverse()
  30. seq.sort()
  31. seq.sortBy()
  32. seq.groupBy()
  33. // Subsets
  34. seq.slice(begin, end)
  35. seq.rest()
  36. seq.butLast()
  37. seq.skip(excludeAfter)
  38. seq.skipLast()
  39. seq.skipWhile()
  40. seq.skipUntil()
  41. seq.take()
  42. seq.takeLast()
  43. seq.taskWhile()
  44. seq.takeUntil()
  45. // Combination
  46. seq.concat()
  47. seq.flatten()
  48. seq.flatMap()
  49. // Reducing the Values
  50. seq.reduce()
  51. seq.reduceRight()
  52. seq.every()
  53. seq.some()
  54. seq.join()
  55. seq.count()
  56. seq.countBy()
  57. seq.isEmpty()
  58. // Search
  59. seq.find()
  60. seq.findLast()
  61. seq.findEntry()
  62. seq.findLastEntry()
  63. seq.max()
  64. seq.maxBy()
  65. seq.min()
  66. seq.minBy()
  67. // Comparison
  68. seq.isSubset()
  69. seq.isSuperset()

Ref