假说论
假说论的目的:在设计一个算法时,应该充分考虑应用场景,最终提出一个假说,从而降低算法的复杂度。
对于DOM操作来说,存在删除元素、新增元素、修改元素、同级间移动元素位置、父子级间移动元素位置等情况,如果考虑全部的情况,Diff算法将变得异常复杂。
DOM Diff 算法提出以下假说:在实际情况中,整棵 DOM 树里,关于父子节点移动的情况是比较少的,所以 Diff 算法实际不会对比父子级间的元素变动, Diff 算法对比只针对同层级的元素。
Vue2 Diff 策略
Diff 算法对比的元素称为 VNode, VNode 有两种类型:标签节点、文本节点。对比时,vnode 为当前最新的节点, oldVnode 是老的节点,最初从 Root 节点开始逐层对比,算法核心包含单个节点、多个兄弟节点的对比。
单个节点对比
单个节点对比相对简单,主要包含以下逻辑:
vnode === oldVnode
新旧节点一致,无需更新;- 如果新旧节点都是静态节点,也无需更新;
- 如果 vnode 是文本节点,对比新旧节点的文本内容,不一致;
- 如果 vnode 是标签节点:
- 新旧节点都有 children,进入多个兄弟节点对比逻辑;
- 如果 vnode 有子节点, oldVnode 没有,往 oldVnode 中插入子节点;反之,则删除 oldVnode 中的子节点;
静态节点: 对于包含表达式、
v-if
、v-for
等其他指令都的节点是非静态的。如果是普通元素、纯文本元素、v-pre
、v-once
等都是静态节点。
多个兄弟节点对比
此算法中采用的是双指针去查找新旧兄弟节点数组中一致部分的节点,具体逻辑如下:
- 先找到新旧数组的首部所有一致节点,并进行单个节点对比及更新;
- 找到新旧数组的尾部所有一致节点,进行单个节点对比及更新;
- 旧数组首部与新数组尾部节点对比,找到则进行单个节点对比更新,并移动旧数组节点的位置;
- 旧数组尾部与新数组首部节点对比,找到逻辑同上;
以上步骤主要是找到没有移动的节点,对于新增、删除、移动位置的节点根据需要逻辑去处理。
Vue3 优化
Vue3 update
性能提升了 1.3~2 倍
,Diff 算法中的优化功不可没,主要采用的优化手段包含:静态标记、静态提升、Diff 算法优化等。
Diff 算法中对于静态标记的节点进行缓存,提升对比、渲染性能。
此外,在多个兄弟节点对比的算法中使用了最长递增子序列的算法,降低了对比时间和节点移动的频次。以下例子可以看出使用最长递增子序列算法后Vue2、Vue3两个版本的处理差异:
oldVnode: [a,b,c,1,2,3,e,f]
, vnode: [a,b,c,3,1,2,e,f]
。
Vue2 处理时首部的a,b,c
和尾部的e,f
都能匹配到,对于vnode 中3,1,2
则需要在oldVnode 中[1,2,3]
节点中进行查找,需要匹配3次,并移动3次节点。
Vue3 前面处理基本一致,差异在于3,1,2
匹配[1,2,3]
时,实际匹配为的子节点序列为3
、[1,2]
,最终移动2次即可。