文章目录
- diff算法是什么
- 比较方式
- 源码分析
-
- patch
- patchVnode
- updateChildren
- 小结
- Vue3中diff算法优化
diff算法是什么
diff
算法是一种通过同层的树节点进行比较的高效算法
其有两个特点:
- 比较只会在同层级进行,不会跨层级比较
- 在dff比较的过程中,循环从两边向中间比较(
首位交叉对比
)
diff
算法在很多场景下都有应用,在Vue中,作用于虚拟dom渲染成真实dom的新旧VNode节点比较
比较方式
diff
整体策略为:深度优先,同层比较
- 比较只会在同层级进行,不会跨层级比较
- 比较的过程中,循环从两边向中间收拢
下面举个vue通过diff算法更新的例子:
新旧VNode节点如下图所示:
第一次循环后,发现旧节点D与新节点D相同,直接复用引旧节点D作为diff后的第一个真实节点,同时旧节点endIndex
移动到C,新节点的startIndex
移动到了C
第二次循环后,同样是旧节点的末尾和新节点的开头(都是C)相同,同理,diff后创建了C的真实节点插入到第一次创建的D节点后面。同时旧节点的endIndex移动到了B,新节点的startIndex移动到了E
第三次循环中,发现E没有找到,这时候只能直接创建新的真实节点E,插入到第二次创建的C节点之后。同时新节点的startIndex移动到了A。旧节点的startIndex和endIndex都保持不动。
源码分析
当数据发生改变时,set
方法会调用Dep.notify
通知所有订阅者Watcher
,订阅者就会调用patch
给真实的DOM
打补丁,更新相应的视图
源码位置:src/core/vdom/patch.js
patch
function patch(oldVnode, vnode, hydrating, removeOnly) {
if (isUndef(vnode)) {
// destory
if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
return
}
let isInitialPatch = false
const insertedVnodeQueue = []
if (isUndef(oldVnode)) {
isInitialPatch = true
createElm(vnode, insertedVnodeQueue) // do
m
} else {
const isRealElement = isDef(oldVnode.nodeType)
if (!isRealElement && sameVnode(oldVnode, vnode)) {
// patchVnode
patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
} else {
// dom
if (isRealElement) {
if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
oldVnode.removeAttribute(SSR_ATTR)
hydrating = true
}
if (isTrue(hydrating)) {
if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
invokeInsertHook(vnode, insertedVnodeQueue, true)
return oldVnode
}
}
oldVnode = emptyNodeAt(oldVnode)
}
return vnode.elm
}
}
}
patch
函数前两个参数位为oldVnode
和Vnode
,分别代表新的节点和之前的旧节点,主要做了四个判断:
- 没有新节点,直接触发旧节点的
destory
钩子 - 没有旧节点,说明是页面刚开始初始化的时候,此时,根本不需要比较了,直接全是新建,所以只调用
createElm
- 旧节点和新节点自身一样,通过
sameVnode
判断节点是否一样,一样时,直接调用patchVnode
去处理这两个节点 - 旧节点和新节点自身不一样,当两个节点不一样的时候,直接创建新节点,删除旧节点
patchVnode
function patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly) {
// 如果新旧节点一致,什么都不做
if (oldVnode === vnode) {
return