以vue3的patch为例
- 首先判断两个节点是否为相同同类节点,不同则删除重新创建
- 如果双方都是文本则更新文本内容
- 如果双方都是元素节点则递归更新子元素,同时更新元素属性
- 更新子节点时又分了几种情况
- 新的子节点是文本,老的子节点是数组则清空,并设置文本;
- 新的子节点是文本,老的子节点是文本则直接更新文本;
- 新的子节点是数组,老的子节点是文本则清空文本,并创建新子节点数组中的子元素;
- 新的子节点是数组,老的子节点也是数组,那么比较两组子节点,更新细节blabla
- vue3中引入的更新策略:静态节点标记等
vdom中diff算法的简易实现
以下代码只是帮助大家理解diff算法的原理和流程
1.将vdom转化为真实dom:
const createElement = (vnode) => {
let tag = vnode.tag;
let attrs = vnode.attrs || {};
let children = vnode.children || [];
if(!tag) {
return null;
}
//创建元素
let elem = document.createElement(tag);
//属性
let attrName;
for (attrName in attrs) {
if(attrs.hasOwnProperty(attrName)) {
elem.setAttribute(attrName, attrs[attrName]);
}
}
//子元素
children.forEach(childVnode => {
//给elem添加子元素
elem.appendChild(createElement(childVnode));
})
//返回真实的dom元素
return elem;
}
2.用简易diff算法做更新操作
function updateChildren(vnode, newVnode) {
let children = vnode.children || [];
let newChildren = newVnode.children || [];
children.forEach((childVnode, index) => {
let newChildVNode = newChildren[index];
if(childVnode.tag === newChildVNode.tag) {
//深层次对比, 递归过程
updateChildren(childVnode, newChildVNode);
} else {
//替换
replaceNode(childVnode, newChildVNode);
}
})
}
if(新Vdom是文本节点){
只需要设置新的Vdom对应的真实dom的文本值就行
} else if(新Vdom不是文本节点){
if(新Vdom有孩子 & 旧的Vdom有孩子){
updateChildren(两者的孩子)
} else if(){
清空新Vdom对应的真实dom的文本值
把新Vdom的孩子的真实dom,添加到新Vdom的真实dom内
} else if(只是旧Vdom有孩子){
把新Vdom的孩子的真实dom删除
} else if(新旧都没有孩子,旧vdom有文本){
把新Vdom的真实dom文本清空
}
}
vue2 和 vue3 diff的区别
Vue.js 2 和 Vue.js 3 在 diff 算法上有一些显著的区别。让我们先来了解一下它们的 diff 算法特点和区别:
Vue.js 2 的 Diff 算法
Vue.js 2 使用的是基于虚拟 DOM 的 diff 算法,具体是采用的是著名的 “Virtual DOM” diff 算法。它的主要特点包括:
-
双端比较:Vue.js 2 的 diff 算法采用双端比较,即同时对新旧节点进行前后比较。这个算法的复杂度是 O(n^3),其中 n 是节点的数量。
-
不可中止:由于双端比较的特性,Vue.js 2 的 diff 算法不可中止,即必须对整个虚拟 DOM 树进行比较,不能在中途中断。
-
Diff 算法细节:在比较节点时,Vue.js 2 的 diff 算法需要考虑节点的类型、属性、子节点等细节,以确定是否需要进行更新。
Vue.js 3 的 Diff 算法
Vue.js 3 引入了一个全新的 diff 算法,名为 Fast DOM
,这个算法相对于 Vue.js 2 的 diff 算法有以下特点:
-
双端向下比较:Vue.js 3 的 diff 算法采用了双端向下比较,是一种更高效的比较方式。它减少了比较的次数,提高了 diff 的性能。
-
可中止:与 Vue.js 2 不同,Vue.js 3 的 diff 算法是可中止的,也就是说在比较过程中可以根据需要提前结束,从而提高了渲染性能。
-
Patch Flag:Vue.js 3 将 diff 过程中的具体操作进行了合并和优化,采用了 Patch Flag 的策略,通过事先标记节点的类型和属性,来减少不必要的比较,从而提升性能。
区别总结
主要区别包括:
-
比较算法:Vue.js 2 使用双端比较的 Virtual DOM 算法,Vue.js 3 使用双端向下比较的
Fast DOM
算法。 -
性能表现:Vue.js 3 的新算法在性能表现上更优秀,特别是在大型应用中能够提供更好的性能。
-
可中止性:Vue.js 3 的 diff 算法可以在必要时中止,这是一个提高性能的重要优势。
总的来说,Vue.js 3 在 diff 算法方面的改进使得其在性能表现上有了显著的提升,特别是对于大型应用或者频繁更新的场景。
为什么Vue.js 3 的 diff 算法可以在必要时中止
Vue.js 3 的 diff 算法可以在必要时中止主要得益于其基于 Proxy 的响应式系统以及新的编译器。
在 Vue.js 3 中,使用了基于 Proxy 的响应式系统取代了 Vue.js 2 中的基于 Object.defineProperty 的响应式系统。Proxy 具有更细粒度的拦截能力,可以在访问、设置和删除属性时触发相应的拦截器函数。这使得 Vue.js 3 能够更精确地追踪应用程序状态的变化。
同时,Vue.js 3 也引入了新的编译器,它可以生成针对模板的优化代码。编译过程中,Vue.js 3 能够通过静态分析模板的结构和指令,生成与之对应的 Patch Flag。Patch Flag 是一个标记,用于在 diff 算法执行过程中快速定位需要更新的节点。
基于 Proxy 的响应式系统和新的编译器共同作用下,Vue.js 3 的 diff 算法可以利用 Patch Flag 来优化 diff 过程。通过比较节点的 Patch Flag,Vue.js 3 可以在必要时中止 diff 过程并跳过不必要的比较。这样的优化降低了 diff 的复杂性和开销,提高了渲染性能。
需要注意的是,Vue.js 3 的 diff 中止能力是有限的。当涉及到更深层次的嵌套和更新时,可能无法完全中止 diff 过程。但整体上,Vue.js 3 的 diff 算法在性能方面得到了显著的提升,特别是对于复杂的应用场景。
为什么说双端向下比较更加高效
双端比较和双端向下比较是 Vue.js 中两种不同的 diff 算法实现方式。
双端比较
在 Vue.js 2 中,采用的是双端比较的 diff 算法。这意味着每次 diff 过程会同时对新旧节点的头部和尾部进行比较。
- 首先,算法会比较新节点和旧节点的头部元素。根据不同的情况进行相应的处理,如元素类型不同、key 不匹配等。
- 接下来,算法会比较新节点和旧节点的尾部元素。同样,会根据不同情况进行相应处理。
- 如果头部或尾部的比较完成后,任一节点已经被处理完(新增或删除),算法会进入下一轮比较。
- 如果头部和尾部的比较都完成后,算法会进一步处理新旧节点之间的中间部分。
这种双端比较算法在比较的过程中可以灵活地根据新旧节点的不同情况进行处理。但由于需要同时比较头部和尾部,相比于其他比较方式可能会存在一些额外的开销。
双端向下比较
在 Vue.js 3 中,采用的是双端向下比较的 diff 算法。这意味着每次 diff 过程会从根节点开始,同时向下比较新旧节点的子节点。
- 首先,算法会比较根节点的新旧子节点数组,根据不同的情况进行相应处理。例如,当两个节点的头部元素相同,则直接对这两个节点的子节点数组进行比较。
- 如果比较完根节点的子节点数组后,任一节点已被处理完,则算法终止比较。
- 如果根节点的子节点数组比较完成后,算法会继续向下比较新旧节点的子节点数组,直到所有节点都被处理完。
双端向下比较的主要优势在于它可以在比较过程中进行中止。因为每次只需向下比较一个节点的子节点,如果在比较的过程中发现某个节点已被处理完,算法可以提前终止后续比较,从而减少不必要的工作量和开销。
相对于双端比较,双端向下比较可以更高效地执行 diff 算法。它通过逐层比较子节点的方式,使得算法能够更快速地中止和跳过不必要的比较。这种优化对于大型应用或者频繁更新的场景能够提供更好的性能表现。