-
Notifications
You must be signed in to change notification settings - Fork 9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
23. 初识immutable #23
Comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
前言
Immutable.js 出来已经有很长一段时间了,只是日常项目中一直用不上。一个是平时没有怎么接触,不了解,另外一个是团队的开发节奏和习惯已经稳定下来了,要改变也不容易。当然了解一下也不差。
不可变的数据一旦生成,就无法被改变,这就要求数据可持久化。可是日常中的引用类型的数据,一旦改变了,就改变了,谈什么持久化数据结构呢?
接触immutable
感受一下immutable的不同:
可以直观的看到在没有使用 immutable 之前, a 与 b 都是同一引用,一旦其中的数据修改了,另一个也会跟着变化,毕竟 a 是等于 b 的。而到了 immutable,就不一样了,当你设置
a.set(0, 0)
的时候,并不会修改 a ,而是返回一个新的数组赋予到 b,所以 a 还是原来的 a。**这就是 immutable 致力解决的疼点:持久化数据解构。**平时使用数据的时候,可能一不小心就会把引用类型数据修改了,导致一些隐藏比较深的问题。尤其是对后来的开发者/维护者而言,意义就更重大了。
在 immutable 里面常见的数据类型有:
其 API 还是很亲民的,基本上字如其名,大致接触一下就能够了解了。常用的用法这里就不做介绍了,需要了解,请移步官方文档。
List 类型
Liit 类型可以说是常用的了,尤其是和 Javascript 里面的 Arry 类似。看一下 List 里面的主要部分:
一个空内容的 List,也就是
List()
,是makeList(0, 0, SHIFT))
生成的,而 SHIFT 的值为 5。传入 List 括号里面的值会被存储在 _root 和 _tail 里面,只是 _root 和 _tail 里面的数据又有以下结构:在 List 列表的生成里,就可以看到,持久化数据的形成,比如看看为何将数据分别保持在 _tail 和 _root 里面,以及又是用何种方式保存;
以设置一个数据为例子,如: List([1]).set(0, 0):
可以看出在 updateList 里面,通过 _capacity 来判断,以 32位 为尺度将 _capacity 切分开来,当 index 大于
((size - 1) >>> SHIFT) << SHIFT
时候,更新 _trail, 否则更新 _root。例如: 当 _capacity 为 33,index 为 32 及其以下的时候,修改的都是 _root,否之则修改 _tail。这个是很好理解的,当数据量达到一定程度的时候,针对靠后的数据单独存储,而靠前的数据放在 _tail,分类处理。只是特别之处在与 _tail 的设计。List 里面的 32 阶 RRB-Tree
_tail 里面采用的是 RRB-Tree 的形式存储数据。这个什么树的先不介绍,先看看看怎么形成,形成的是什么,继续看上面的 updateList 方法,里面用到了 updateVNode 方法来生成 _tail:
在这里可以看到 value/newLowerNode 是赋值给
newNode.array[idx]
, 而 idx 并不是等于 index。以 _capacity 为 65 的 List 为例子,其 _level 为 5。当 index 为 60 的时候,有以下行为:可以看出这里生成个二维的数组,其中每个子节点的长度最大为 32,于是这就构成了一个 32阶的树结构。至于为什么阶长是 32,在代码中是这么解释的:
这不是逗我嘛。。。什么都没有写好吧,而且 github 里面最早的版本也是这么写的。。。。最后还好找到是有测试实验数据证明 32 也就是 SHIFT 为 5 是最佳实践。至于为什么要采用这种结构,不是本文要考虑的,将在下篇中给出来。
最后数据生成的结构如下如所示:
get 方法里面也是差不多的,通过 _capacity 来判断。
Map 类型结构
同样的先看看一个空的 Map() 的结构:
这里就没有 _tail 结构的,所有数据都放在 _root 里面。只是 _root 里面的数据并非总是简单,采用了 Trie Nodes 的方式存储数据。 immutable 里面将对象的键值对转换为数组表示,即 [key, value] 的形式。
存储的数据分为以下级别:
withMutations 操作
每次对 immutable 的数据类型操作的时候,都会返回一个新的数据,这样就存在一个问题,如果需要像原本一样对一个数据不断0操作,如不断的向Arry 里面 push 新值。对于 List,每次都返回一个新 List 岂不是多了很多中间变量,多了很多蹩脚的操作?于是就有了 withMutations 操作。如:
上面为原先不使用 withMutations 的方法,每次 push 之后都需要重新对 a 重新赋值,才能保证正确性。而用了 withMutations 方法之后,不再需要创建中间 List,减少了复杂度,基本上运行时间比前一种快上好几倍。当然 withMutations 不会修改 a,所以需要将中间 List 赋值到 a。
为什么 withMutations 会有这样的效果呢?以 A(List) 为例子,当使用 withMutations 时候,其首先,生成的中间变量 B(List),其属性值就是 A 的属性值,没有任何更改,当然如果 A 没有 __ownerID, B 的 __ownerID 会被设置上。但是对 B 进行 push/pop/set 等等操作的时候,A 其实是没有受到任何影响的。
如在 withMutations 操作里面 set 数据的时候, 上面 if 语句是无法通过的,因为 A 的数据里面是没后 ownerID 的,相当于给 A 的数据浅拷贝一次到 B 里面,但是在 withMutations 里面再次 set 的时候,这时上面的 if 语句是可以通过的,于是又能节省不少运行时间。类似的 Map 也是相似的,这里就不再提到了。
其他
immutable.js 类型的一层层继承关系,刚开始的看的时候会觉得有点乱,不管哪里都有 extends。细细看下来还是挺不错的。Immutable.js 还有个 Seq 类型,其特点就是懒。懒的意思是,直到获取使用了才开始计算。一开始觉得很神奇,居然有用数据的时候才计算的。。。well,后来一看这得益于一系列的 API,主要就是把调用方法函数数据什么的统统用闭包给存起来。。。。。。。好吧。
ps:immutable.js 最神奇的地方还是在于其数据结构,下篇文章将会好好讲数据结构。
The text was updated successfully, but these errors were encountered: