Vue3 diff
最长递增子序列+双端diff
理念
- 相同的前置和后置元素的预处理,考虑边界情况,减少移动;
- 最长递增子序列(动态规划、二分法),判断是否需要移动
操作
- 前置与后置预处理
- 判断是否需要移动
递增法:根据新列表剩余的节点数,创建一个source数组,填入-1,存储新节点在旧节点的位置,再算出source中最长递增子序列,用于移动DOM,如旧节点在新列表中没有,则直接删除 - 移动DOM:根据source对新列表进行重新编号,找到source的最长递增子序列在原本数组中的索引,创建一个数组保存每个值的最长子序列在数组中的index,然后从后向前遍历source
- 当前index=-1,则创建DOM节点插入队尾
- 当前index为最长递增子序列中的值,则不需要移动,判断是否有全新的节点添加
- 当前index不在最长递增子序列中,则移动,将DOM节点插入队尾
细节
- 同层级比较
- 节点类型判断:不同类型直接替换
- key值比较:相同key可复用
- 属性比较:只更新变化的属性
Vue2 diff
双端比较
缺乏事件切片能力,除了高帧率动画,其他场景都可以使用防抖和节流提高响应性能
理念
新列表和旧列表两个列表的头和尾互相对比,在对比的过程中指针会逐渐向内靠拢,直到某一个列表的节点全部遍历过,对比停止
过程
- vue的diff算法是平级比较,不考虑跨级比较的情况,即只有在新旧children都为多个子节点时才需要使用diff算法进行同层级比较
- 内部采用深度递归的方式+双指针的方式进行比较
- 旧children和新children各有两个头尾的变量startIdx和EndIdx,头头、尾尾、头尾、尾头进行对比,一共四种比较方式
- 使用旧列表的头一个节点与新列表的头一个节点对比
- 使用旧列表的最后一个节点与新列表的最后一个节点对比
- 使用旧列表的头一个节点与新列表的最后一个节点对比
- 使用旧列表的最后一个节点与新列表的头一个节点对比去
寻找key相同的可复用的节点,如果找到则停止后面的寻找;
- 四种方式都没匹配,如果设置了key,就用key进行比较,拿新列表中第一个节点去旧列表中找与其key相同的节点
- 在旧列表中找到了:移动找到的节点,旧列表中的节点改为undefined,移动过的节点不需要再进行节点对比;
- 在旧列表中没找到:创建新节点放到最前面在比较中,变量会往中间靠,如果startIdx>EndIdx,表明旧children和新children至少有一个已经遍历完成,结束比较
- 旧children和新children各有两个头尾的变量startIdx和EndIdx,头头、尾尾、头尾、尾头进行对比,一共四种比较方式
- vue的diff算法称为patching算法,diff执行时刻是组件内响应式数据变更触发实例执行其更新函数,再次执行render函数得到最新的虚拟DOM,然后执行patch函数,并传入新旧两次的虚拟DOM,对比找到变化,将其转化为对应的DOM操作
- patch过程是递归过程,遵循深度优先,同层比较的策略
Vue3 diff和Vue2 diff比较
- 最长递增子序列算法
减少不必要的DOM操作,提升性能 - 静态标记
更新时跳过静态节点 - 缓存数组
将新旧VNode数组缓存,只对数组中不同的VNode对比,减少比对次数,提升性能 - 动态删除数组
异步队列方式,将多个删除操作合并