一、简介
Vue
的diff
算法是同层比较,不会跨层比较,时间复杂度为O(N)。
主要使用首尾比较法(头-头,头-尾,尾-头,尾-尾)。Vue2
与Vue3
的diff
算法主要区别是处理完首尾节点后,对剩余节点的处理方式。
二、Vue2 diff算法过程
- 新旧虚拟
DOM
分别创建首尾两个指针 - 依次头-头,头-尾,尾-头,尾-尾(
startIndex1
,startIndex2
,endIndex1
,endIndex2
)比较,若在比较过程中发现指向的节点内容一致,则直接复用旧结点作为diff
创建的节点。若这四种情况都不满足,则diff将创建当前新虚拟DOM
的节点插入到diff
队列后面,并且将新虚拟DOM
的startIndex2
指向下一个节点。 - 当其中一条虚拟
DOM
的endStart
<startIndex
,比较结束。 - 随后根据新老节点的数目,来进行插入或删除操作。若新节点数目大于老节点则需要把多出来的节点创建出来加入到真实
DOM
中,反之若老节点数目大于新节点则需要把多出来的老节点从真实DOM
中删除。 - 至此整个
diff
过程就已经全部完成了。
三、Vue3 diff算法过程
-
新旧节点从头开始比较,相同直接复用执行
patch
,直到比较到两节点不同为止。 -
新旧节点从尾开始比较,相同直接复用执行
patch
,直到比较到两节点不同为止。 -
处理完以上两种情况,剩下3种情况。
-
老节点先遍历完,新节点还剩余:创建剩余新节点。
-
新节点先遍历完,老节点还剩余:删除剩余旧节点。
-
新老节点都有剩余:在新老剩余节点中寻找可复用节点。创建新节点剩余节点对应的映射表
keyToNewIndexMap
。再创建一个数组newIndexToOldIndexMap
,用来存储新节点数组中的剩余节点在旧节点数组上的索引,后面将使用它计算出一个最长递增子序列,并初始化数组为0。
-
遍历老节点,存储新节点数组中的剩余节点在旧节点数组上的索引。
-
计算
newIndexToOldIndexMap
数组,得到最长增长子序列(贪心 + 二分)。
-
遍历新节点,根据最长增长子序列进行移动、添加、删除节点。第一个节点为h,在newIndexToOldIndexMap中值为0,说明是新增的节点,创建。第二个节点为e,索引为3,与最长增长子序列中3命中,跳过。第三个节点为d,索引为2,与最长增长子序列中2命中,跳过
第四个节点为c,索引为1,与最长增长子序列中1命中,跳过。第五个节点为f,索引为0,均不符合,进行hostInsert,将节点f的真实dom移动到节点nextChild节点c的前面。遍历结束完成 。
四、总结
- vue2、vue3 的 diff 算法实现差异主要体现在:处理完首尾节点后,对剩余节点的处理方式。
- vue2 是通过对旧节点列表建立一个 { key, oldVnode }的映射表,然后遍历新节点列表的剩余节点,根据newVnode.key在旧映射表中寻找可复用的节点,然后打补丁并且移动到正确的位置。
- vue3 则是建立一个存储新节点数组中的剩余节点在旧节点数组上的索引的映射关系数组,建立完成这个数组后也即找到了可复用的节点,然后通过这个数组计算得到最长递增子序列,这个序列中的节点保持不动,然后将新节点数组中的剩余节点移动到正确的位置。