一、Diff 概念
Vue 基于虚拟 DOM 做更新。diff 算法的核心就是比较两个虚拟节点的差异。
Vue 的 diff 算法是平级比较(如图,父级和父级比较,儿子和儿子比较,孙子和孙子比较),不考虑跨级比较的情况(因为在实际场景上极少用到,且为了性能考虑所以不考虑跨级比较的情况)。
内部采用深度递归的方式 + 双指针的方式进行比较。
二、Diff 比较流程
1、先比较两个虚拟节点是否是相同节点【key、tag】
主要是比较两个节点的 key 属性和 tag 标签,有任何一个不一样就说明这两个元素不是相同元素
2、如果是相同节点,下一步是比较属性并复用老节点 (将老的虚拟 DOM 复用给新的虚拟节点 DOM)
如果不一样,就会删除老节点,创建新节点
3、如果两个父节点相同的话,那么就比较儿子节点,需要以下几种情况:
① 老的没儿子,新的有儿子。
那么就创造新的儿子节点,直接插入元素中
② 老的有儿子,新的没儿子。
直接删除老的儿子节点
③ 老的儿子是文本,新的儿子也是文本。
如果不一致,直接更新文本节点即可
④ 老的儿子是一个列表,新的儿子也是一个列表 updateChildren方法
两个列表的比较,也是diff算法的核心所在。一个数组跟另一个数组的比对,有差异就更新。
三、两个列表的比较:优化比较
常见DOM操作:追加、删除、倒序、反序
对应的优化比较策略:头头、尾尾、头尾、尾头
优化方案:
整个优化采用双指针的方式,也就是在新老节点的头部和尾部分别插入2个指针,在头部和尾部都有一个指针。在比对的过程中,采用的是有一方头尾指针重合的话,就意味着节点遍历结束。这个时候就会终止 diff 算法。
① 头和头节点比较【头头】
首先,比较A和A是否相同,相同则指针向后移动。 B和B,C和C,这时候指针已经越界,diff 算法结束。将 D 直接插入到节点中就可以了。
向尾部插入新节点:
替换尾部不相同的节点:
② 尾和尾节点比较【尾尾】
指针从尾部开始比较新老节点。
向头部插入新节点:
替换头部不相同的节点:
③ 头和尾节点比较【头尾】
先比较头和头是否相同,再比较尾和尾是否相同。都不相同的时候,那就是用头部比较。
将老节点的头部跟新节点的尾部比较,相同则将老节点移动到后面去。
后面就是按照头头比较、尾尾比较、头尾比较这种顺序走。
④ 尾和头节点比较【尾头】
先比较头和头是否相同,再比较尾和尾是否相同。都不相同的时候,那就是用头尾比较,再不相同就是尾头比较。
将老节点的尾部跟新节点的头部比较,相同则将老节点移动到前面去。
头指针比对成功,头指针需要往后移动一格。尾指针比对成功,尾指针需要往前移动一格。
⑤ 倒序
老规矩,递归的使用以下比对策略:先比较头头、尾尾,再比较头尾、尾头
固定一个节点,最后把每个节点进行移动来进行复用。并没有进行重新创建操作
5.比对查找进行复用
乱序的情况下:
Vue3 中采用最长递增子序列来实现 diff 优化。