现有如下DOM结构:
<div class="message">
<div class="time">
<p>时间:</p>
<p>{{time}}</p>
</div>
<div class="position">
<p>地点:</p>
<p>{{position}}</p>
</div>
<div class="name">
<p>人物:</p>
<p>{{name}}</p>
</div>
</div>
对应的DOM树为:
蓝色的为静态节点,红色的为动态节点。
假设现在触发了一次数据变化,上述DOM结构中三个变量(time、position、name)均发生了变化。
(注:
VDOM
——虚拟DOMrender函数
——渲染函数vnode
——虚拟节点)
Vue2的整体处理流程:
Template模板
编译为render函数
- 根据
render函数
创建VDOM
- 将
VDOM
渲染为真实的DOM - 当数据发生变化后需要更新DOM时,vue会根据新的DOM结构创建新的
VDOM
来和现有的VDOM
来比较,共需创建10个vnode
,实际创建10个vnode
- 然后采用
Diff算法
比较新旧VDOM
,判断需要变化的地方。使用同级比较来降低复杂度,最终比较了10个节点(1+3+6),发现了3个需要更新的节点 - 更新这三个节点
Vue3的整体处理流程:
Template模板
编译为render函数
,该过程有如下几点优化。- 静态节点复用:此时会将静态节点(如上例中的3个P.label)从
render函数
中提升出来,并在每次渲染时重用相同的静态节点。此外,当有足够多的连续静态元素时,它们将被压缩成一个“静态 vnode”,其中包含所有这些节点的纯 HTML 字符串(示例)。这些静态 vnode 是通过直接设置挂载的innerHTML。它们还在初始挂载时缓存其对应的 DOM 节点 - 如果在应用程序的其他地方重复使用相同的内容,则使用 native 创建新的 DOM 节点cloneNode(),这非常有效。 - 补丁标志:每个节点都会标明补丁标志(一个数字),补丁标志代表节点的更新类型,即后续该节点如果变化,会怎样变化,需要如何处理。Vue3内部有很多补丁标志,每个补丁标志对应着不同的处理逻辑,有助于vue更精准快速的更新节点。使用补丁标志,Vue 能够在使用动态绑定更新元素时做最少的工作。
- 静态节点复用:此时会将静态节点(如上例中的3个P.label)从
- 根据
render函数
创建VDOM
,该过程有如下优化。- 树扁平化:
VDOM
树的根是使用特殊createElementBlock()
调用创建的“块”,每个块跟踪具有补丁标志的任何后代节点(不仅仅是直接子节点)。所以当该组件需要重新渲染时,它只需要遍历扁平化的树而不是完整的树。它大大减少了虚拟DOM协调过程中需要遍历的节点数量。模板的任何静态部分都被有效的跳过。
- 树扁平化:
- 将
VDOM
渲染为真实的DOM - 当数据发生变化后需要更新DOM时,vue会根据新的DOM结构创建新
VDOM
来和现有的VDOM
来比较。在创建新VDOM时会复用render函数
中提升出来的静态节点,所以共需创建10个vnode
,复用了3个vnode
,所以实际创建了7个vnode
。 - 比较新旧
VDOM
,判断需要变化的地方,最终比较了7个节点,发现了3个需要更新的节点,该过程有如下优化。- 同级比较:使用同一层级的节点来进行比较,以降低复杂度。Vue2也是这样做的。
- 跳过相同节点:然后当渲染器注意到旧
vnode
和新vnode
是同一个时(复用的静态节点)会直接跳过比较 - 事件监听缓存:事件监听缓存是针对于有事件绑定的节点在Diff算法上的优化,因为在正常开发中,节点上的
@click
等事件是不会随着数据变化而变化的,所以在Vue3中哪怕静态节点上绑定了@click
等事件,DOM更新时Diff算法也不会去比较该静态节点。如:<div @click="submit">提交</div>
。该节点在vue2中每次都会参与Diff比较,因为它有@click
绑定,但事实上任何数据变了也不会影响submit
函数,所以最好的情况就是将该节点看做一个静态节点,然而,vue3就是这样做的!Vue3会将该节点看做一个静态节点,永远不会参与Diff算法的比较。
- 根据需要更新的三个节点的补丁标志进行定制化更新节点。