diff 算法总结
简单来说,diff 算法有以下过程
- 同级比较,再比较子节点
- 先判断一方有子节点一方没有子节点的情况(如果新的 children 没有子节点,将旧的子节点移除)
- 比较都有子节点的情况(核心 diff)
- 递归比较子节点
patch 算法:
oldVnode: 旧虚拟节点或 DOM 节点(首次渲染时)
vnode: 新虚拟节点
- 若 vnode 不存在,直接销毁 oldVnode;
- 若 oldVnode 不存在,直接根据 vnode 创建 DOM 节点;
- 若 oldVnode 和 vnode 都存在,则判断 oldVnode 是否为真实的 DOM 节点:
- 若 oldVnode 不是真实 DOM 节点且根据 sameVnode 方法判断 oldVnode 和 vnode 是否相同,相同则直接进行 patchVnode 对比。
- 若 oldVnode 是真实 DOM(首次渲染),并创建一个空节点替换 oldVnode。根据 vnode 创建 DOM 节点。
patchVnode 算法:
- oldVnode === vnode,直接返回,流程结束。
- oldVnode !== vnode,若都是静态节点,且 key 相同,vnode 是克隆的或者使用 v-once,则直接将 oldVnode 的组件实例赋值给 vnode,流程结束。
- 当 vnode 不是文本节点时,
- 当 oldVnode 和 vnode 都存在子节点时,使用 updateChildren() 对比子节点;
- 若 oldVnode 无子节点,若 oldVnode 是文本节点,则将文本置空,并根据子虚拟节点创建 DOM 节点;
- 若 vnode 无子节点,则直接移除 oldVnode 的子节点;
- 若 oldVnode 是文本节点,则直接将 oldVnode 的文本置空;
- 当 vnode 是文本节点,且 oldVnode.text !== vnode.text 时,直接替换 text
updateChildren 算法:
从 oldVnode 和 vnode 的 children 双端进行比较,借助 key 值找到可复用的节点,再进行相关操作。
递归比较子节点:
- sameVnode(oldStartVnode, newStartVnode):首部对比,相同则 patchNode ,比较下一个首部;
- sameVnode(oldEndVnode, newEndVnode):尾部对比,相同则 patchNode ,比较下一个尾部;
- sameVnode(oldStartVnode, newEndVnode):首尾对比,相同则 patchNode ,并将 oldStartVnode 对应的 DOM 节点移动到最后,比较下一个首尾;
- sameVnode(oldEndVnode, newStartVnode):尾首对比,相同则 patchNode ,并将 oldEndVnode 对应的 DOM 节点移动到最前,比较下一个尾首;
- 其他,若 newStartVnode 的 key 在 oldVnode 的 children 中找不到,则根据 newStartVnode 直接创建 DOM 节点;若找到相同的 key,且根据 sameVnode 方法判断,相同则 patchNode ,将 oldVnode 的 child 赋值为 undefined,并将 newStartVnode 对应的 DOM 节点移动到 oldStartVnode 对应的 DOM 节点前;若 key 相同但元素不同,则直接创建 DOM 节点。
若 vnode.children > oldVnode.children,则无需对比,直接根据多余子节点创建 DOM 节点 若 vnode.children < oldVnode.children,则无需对比,直接移除 oldVnode 的多余子节点。