ImmutableJs出现背景
JavaScript中的Plain Object
,一般是以引用形式存在的可变数据(Mutable Data),这样设计的目的可能是为了节省CPU与内存的开销;每次进行对象修改时直接改变对象本身(changed in-place rather than return a new object)
一般mutable data进行数据操作时比较简单,但是当在一些大型复杂应用中使用mutable data时可能会导致数据无法溯源,因为很多地方都可以修改这个数据,导致难以调试与维护;
当我们的项目中仅在很少的地方用到
Immutable Data
时,我们一般可以通过shallowCopy与deepCopy来简单实现,但是会造成CPU与内存的浪费 (camsong)
因此,immutable.js应运而生
ImmutableJs 优点
- 避免
Mutable data
带来的难以数据回溯问题; - 节省内存与CPU
- 并发安全(不用担心数据更改不一致问题)
- undo/redo,copy/Paste,时间旅行等功能(可以通过列表保留历史记录进行各种相关操作)
- 函数式编程(ImmutableJs的设计本身就是遵循函数式编程思想)
ImmutableJs实现机制
持久化数据结构(Persistent Data Structure)
这是ImmutableJs实现的根本
被修改时总是保持该对象的旧版本并返回一个新的版本;即旧版本仍然是可访问的;
所有操作总是返回数据结构的一个新版本并保持原始数据的完整性,而不是就地修改原始数据;
结构化共享(Structural Sharing)
由旧对象创建新对象时仅需要创建和修改部分需要更新的节点,其他节点复用旧对象的节点,因此可以更加高效和节省内存;
结构化共享
是ImmutableJs
性能优化的一个重要手段
数据结构与性能优化
ImmutableJs的结构化共享实现是基于tries
的,包括Hash Array Map tries
和vector tries
;
vector tries
限制了节点长度为32,这是通过实验验证得出一个合理值,是对查找和更新的trade-off;
单层过长的节点有利于查找,但不利于更新;
使用数字分区(Digit Partition)
与位分区(Bit Partition)
,immutable.js
采用位分区可以有效提升查找速度,但是是以空间为代价的;
同时为了提供空间利用率,使用HAMT(Hash array mapped trie)
对树高和树宽进行了压缩处理;树高压缩采用了移除非必要节点的方式,节点内部压缩(树宽)压缩采用了Bitmap
方式避免不必要的位置索引存储;
这里bitmap
压缩仅对子节点数小于16的节点进行处理,因此可以看到Immutable.Map
有三种节点类型:
HashArrayMapNode
BitmapIndexedNode
ValueNode
why 32
是2的整数次幂,便于计算;同时,对查询和更新进行了trade-off
参考文献
- https://github.com/vivatoviva/Interview-Frontend-2020/issues/16
- https://github.com/ascoders/weekly/issues/14
- https://github.com/camsong/blog/issues/3
- https://medium.com/@dtinth/immutable-js-persistent-data-structures-and-structural-sharing-6d163fbd73d2
- https://hypirion.com/musings/understanding-persistent-vector-pt-1
- 深入探究Immutable.js的实现机制(一)
- 深入探究Immutable.js的实现机制(二)