上一篇文章中,我们详细讲了一下vue2的diff算法,和他的算法源码过程。
然而vue3在diff算法上有了很好的改进。下面来一起探索一下吧。
Vue3的diff算法核心
基本原理
Vue3引入了一个全新的编译策略和运行时优化,包括对Diff算法的改进。Vue3的Diff算法带来了更好的性能和更少的内存消耗,主要得益于以下几点:
- 双端比较优化:Vue3继续使用了双端比较算法,但是在细节上进行了优化,比如对于相同节点的处理更加高效。
- 静态节点提升:Vue3在编译时会对静态节点进行提升,这些节点在更新时不会被重新创建,而是直接复用,大大减少了渲染成本。
- 支持碎片化(Fragment):Vue3支持碎片化,允许组件有多个根节点,这在Vue2中是不支持的。
- 区块树(Block Tree):Vue3引入了区块树概念,它可以跳过静态内容,快速定位到动态节点,减少Diff时的比较次数。
- 编译时优化:Vue3在编译时会对模板进行静态提升,将不会变化的节点和属性提取出来,避免在每次渲染时都重新创建。这样可以减少虚拟DOM树的创建和销毁过程,提高性能。
diff流程
我们举例讲解:
- 老的 children:[ a, b, c, d, e, f, g ]
- 新的 children:[ a, b, f, c, d, e, h, g ]
初始化代码
let i = 0; // 新老vnode遍历的开始下标
const l2 = c2.length; // 新vnode的遍历长度
let e1 = c1.length -1; // 老vnode的末尾下标
let e2 = l2 -1; // 新vnode的末尾下标
头部节点对比
首先对新老VNode节点的头部开始进行比较,寻找相同节点,如果有相同节点满足sameVnode(可以复用的相同节点)则直接进行patch(该方法进行节点复用处理)。
while (i <= e1 && i <= e2) {
const prevChild = c1[i];
const nextChild = c2[i];
// 如果不是相同的节点,遍历终止
if (!isSameVNodeType(prevChild, nextChild)) {
break;
}
// 是相同节点,继续往后遍历,新老节点末尾索引加1
patch(prevChild, nextChild, container, parentAnchor, parentComponent);
i++;
}
我们根据新老children来看,头部节点对比完了,i应该从0变为了2。
尾部节点对比
首先对新老VNode节点的尾部开始比较,寻找相同节点,如果有相同节点满足sameVnode(可以复用的相同节点)则直接进行patch(该方法进行节点复用处理)。
while(i <= e1 && i <=e2) {
// 从右向左取值
const prevChild = c1[e1];
const nextChild = c2[e2];
// 如果不是相同节点,遍历终止
if (!isSameVNodeType(prevChild, nextChild)) {
break;
}
// 是相同节点,继续往后遍历,新老节点末尾索引减1
patch(prevChild, nextChild, container, parentAnchor, parentComponent);
e1--;
e2--;
}
我们根据新老Children来看,尾部节点对比完之后,e1变为了5,e2变为了6。
剩余节点对比
这时可能出现以下情况
- 老节点先遍历完,新节点还有剩余:创建剩余新节点。
if (i > e1 && i < e2) {
// 如果是这种情况的话就说明e2也就是新节点的数量大于旧节点的数量
// 这时就要新增vnode
// 应循环c2,新的节点有可能需要添加尾部,也可能添加到头部,所以需要指定添加的问题
// 要添加的位置是当前的位置(e2开始)+1
// 因为对于往左侧添加的话,应该获取c2的第一个元素