自从react流行起来后,前端开发者们对虚拟DOM的研究就从未停止过,现在开源世界里已经有了非常多优秀的虚拟DOM库供大家使用。虚拟DOM这项技术能流行起来大概有三个原因。
- 通过减少真实DOM操作提高系统运行效率
- 降低维护状态 -> 视图的复杂程度
- 帮助开发者轻松实现前端组件化
一、虚拟DOM的工作过程
虚拟DOM以virtual node为基础单元构造虚拟DOM树,然后随系统状态的变化来操作真实DOM。大致的工作过程如下:
- 创建virtual node,构建起虚拟DOM树
- 根据虚拟DOM树创建真实DOM树
- 系统状态变化,产生新的虚拟DOM树
- 拿新树与旧树做diff操作,分析出两棵树的差异
- 针对差异对真实DOM做修改
二、虚拟DOM节点
虚拟DOM节点(virtual node)对应真实DOM中的HTML Element,它的构造非常简单,只需要提供标签名,属性,子节点和一个用于区分兄弟节点的key。它的结构和真实DOM里的元素非常类似。
function VirtualNode(tagName, properties, children, key) {
this.tagName = tagName
this.properties = properties || null
this.children = children || []
this.key = key != null ? String(key) : undefined
...
}
一层一层的virtual node组成了一棵虚拟DOM树
三、diff
每棵树都有一个根节点,从根节点做遍历是分析树最快的方式。遍历的方式可以选择深度优先遍历或者广度优先遍历。为了降低diff的复杂度,一般的虚拟DOM库都会设置两个前提:
- 类型不同(tagName不同)的两个virtual node被认为是完全不一样的,即使它们的所有属性、子节点和key都一样
- 对于同一层级的一组子节点,它们可以通过唯一的 key值进行区分
接下来是具体的diff过程
1.不同类型节点的对比
对于不同类型的两个节点,反映到真实DOM上的操作是,直接删除旧的节点,然后插入新的节点
2.相同类型节点的对比
根据key值分两种情况, key值相同的两个节点会接着比较属性和子节点,key值不同的情况下,和不同类型节点一样,删除旧的节点,插入新的节点。
3.virtual node节点列表对比
这里需要强调的是,节点列表里的每一项都没有key值的情况下,旧列表中的所有项都不会被重新利用,反映到真实DOM节点就是直接用新的元素列表代替旧的元素列表,这样情况没有任何可以优化的地方。所以这里说的是每一项带有唯一key值的virutal node节点列表之间的对比。
节点列表之间的对比类似于比较两个字符串
A: [a b c d e f g]
B: [a c b h f e g]
首先分析出可重用的节点,如a
,b
,c
,e
,f
,g
,通过对比两个列表的key值得出一个新的临时列表C,在B列表中没有的用-1表示,把B列表中存在但A列表不存在的放到最后。
C: [a b c -1 e f g h]
然后做一下处理,记录下值是-1的节点的索引,这些节点对应的DOM元素会被删除,在C列表中也删除-1的值
C: [a b c e f g h]
接下来同时遍历B、C两个列表,对比相同位置下的节点,根据下面的规则来制定出移动真实DOM元素次数最少的步骤。
- 值相同,对应的真实DOM不移动,如
a
节点 - 值不同,记录下当前索引,把C列表中的节点也记录下来,它对应的DOM元素会被插入到刚才记录下的索引处,如
c
会被插入到列表第二的位置
最后,得到两个列表对比的分析结果
四、把差异应用到真正的DOM树上
和diff过程采用的遍历方式一样,遍历一次整个真实DOM树,最后根据记录的差异来操作DOM元素。
五、小结
本文大致讲解了虚拟DOM的工作过程和Diff原理,参考的是virtual-dom这个虚拟DOM库,同时我自己用这个库实现了一个todo-list应用 todolist-virtualdom
,帮助大家理解虚拟DOM的使用方法和工作过程。
参考资料
深度剖析:如何实现一个 Virtual DOM 算法
深入浅出 React(四):虚拟 DOM Diff 算法解析
Reconciliation
网友评论