虚拟DOM
1 什么是虚拟DOM
虚拟DOM是一层对真实DOM的抽象,以JavaScript 对象 (VNode 节点) 作为基础的树,用对象的属性来描述节点,通过渲染过程的操作将其转化为真实的DOM对象
虚拟DOM 一般是一个 Object对象。并且最少包含标签名 (tag)、属性 (attrs) 和子元素对象 (children) 三个属性,不同框架对属性的命名不同。、
通过对虚拟DOM构成的抽象树进行创建节点,删除节点以及修改节点的操作, 经过diff算法得出一些需要修改的最小单位,再更新视图,减少了dom操作,提高了性能
2 虚拟DOM的优点
-
虚拟DOM可以将大量多次的DOM操作合并为一次操作,例如将1000次的DOM修改合并为1次,减少了操作DOM的频率
-
虚拟DOM可以通过DOM diff算法减少不必要的DOM操作,减少DOM的修改范围
-
虚拟DOM实际上是给我们找了一条最短,最近的路径,并不是说比DOM操作的更快。
-
虚拟DOM可以借助不依赖真实DOM结构的优势,实现更有效的跨平台兼容
3 虚拟DOM的缺点
- 虚拟DOM通过diff算法和批量处理策略,将所有的DOM操作整合,一次性去改变真实的DOM,但在首次渲染上,虚拟DOM会多了一层计算,可能会比html渲染的要慢
- 需要额外的渲染函数创建DOM,例如Vue中的h函数或React中的createElement,或者依赖打包工具通过 JSX或者vue-loader 来简化成 XML 写法。
DOM diff
1 什么是DOM diff算法
比较改变前后两个虚拟DOM树区别的算法一般称为DOM diff算法 一般有以下的比较方式
- Tree diff
将新旧两棵树逐层对比,找出哪些节点需要更新
如果节点是组件就看 Component diff
如果节点是标签就看 Element diff
- Component diff
如果节点是组件,就先看组件类型
类型不同直接替换(删除旧的)
类型相同则只更新属性
然后深入组件做 Tree diff(递归)
- Element diff
如果节点是原生标签,则看标签名
标签名不同直接替换,相同则只更新属性
然后进入标签后代做 Tree diff(递归)
具体实现
以React为例
- 两棵树如果完全比较时间复杂度是
O(n^3) React的Diff算法的时间复杂度是O(n)。只在相同层级之间比较两棵树的节点,放弃了深度遍历,考虑到现实中前端页面跨层级移动DOM元素的行为相对较少,所以这样做是最优的。
DOM diff的操作一共分为四种:
-
第一种是最简单的,节点类型变了,这个过程称之为
REPLACE。直接将旧节点卸载(componentWillUnmount)并装载新节点(componentWillMount)就行了 -
第二种也比较简单,节点类型一样,仅仅属性或属性值变了,这个过程称之为
PROPS。此时不会触发节点的卸载(componentWillUnmount)和装载(componentWillMount)动作。而是执行节点更新(shouldComponentUpdate到componentDidUpdate的一系列方法) -
第三种是文本变了,文本对也是一个
Text Node,也比较简单,直接修改文字内容就行了,我们将这个过程称之为TEXT -
第四种是移动,增加,删除子节点,我们将这个过程称之为
REORDER
diff算法的优点
减少对于DOM的操作,只需要对有变化的元素进行修改,提高效率
缺点
在同级操作中会出现BUG
如上图所示,如果不用Key标识DOM元素,则插入第一个元素时,右侧对应的元素没有和左侧插入后的结果匹配
key是为Vue中的VNode标记的唯一id,在patch过程中通过key可以判断两个虚拟节点是否是相同节点,通过这个key,我们的diff操作可以更准确、更快速diff算法的过程中,先会进行新旧节点的首尾交叉对比,当无法匹配的时候会用新节点的key与旧节点进行比对,然后检出差异- 尽量不要采用索引作为
key - 如果不加
key,那么vue会选择复用节点(Vue的就地更新策略),导致之前节点的状态被保留下来,会产生一系列的bug
key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据新数据生成新的虚拟DOM,随后Vue进行新虚拟DOM和旧虚拟DOM的差异比较;比较规则如:\
- 旧虚拟DOM中找到了与新虚拟DOM相同地key:如果虚拟DOM内容没有变化,则直接使用之前的真实DOM;如果虚拟DOM中内容发生改变,则生成新的真实DOM,随后替换掉页面之前的真实DOM;\
- 旧虚拟DOM中未找到与新虚拟DOM相同地key,则创建新的真实DOM,随后渲染到页面。