vue diff实现
Vue 的 Diff 算法实现
Vue 的虚拟 DOM Diff 算法(称为 "patch" 过程)通过高效比较新旧虚拟节点(VNode)来最小化 DOM 操作。其核心思想是基于以下策略:
同层级比较
Vue 仅在同一层级的虚拟节点间比较,不跨层级递归。若节点类型不同(如 div 变为 span),直接销毁旧节点并创建新节点。
双端比较(针对列表)
对于列表节点(如 v-for),Vue 使用 双端指针(头尾共 4 个指针)进行快速比对,优先处理以下场景:
- 头头匹配:新旧列表的头节点相同。
- 尾尾匹配:新旧列表的尾节点相同。
- 头尾匹配:旧头与新尾相同(说明节点移动到尾部)。
- 尾头匹配:旧尾与新头相同(说明节点移动到头部)。
若无匹配,则通过 key 查找旧节点中是否存在可复用的节点。
Key 的作用
key 是唯一标识,帮助 Vue 识别节点的稳定性。若 key 相同且节点类型相同,则复用 DOM 元素,仅更新属性或子节点。
核心代码逻辑示例
以下是简化后的 Diff 流程代码(非完整源码):
function patch(oldVnode, newVnode) {
// 类型不同,直接替换
if (oldVnode.tag !== newVnode.tag) {
replaceNode(oldVnode, newVnode);
return;
}
// 复用 DOM 元素
const el = (newVnode.el = oldVnode.el);
// 更新属性
updateProps(el, oldVnode.props, newVnode.props);
// 处理子节点
if (oldVnode.children && newVnode.children) {
updateChildren(el, oldVnode.children, newVnode.children);
} else if (newVnode.children) {
// 旧无子,新有子:添加
appendChildren(el, newVnode.children);
} else if (oldVnode.children) {
// 旧有子,新无子:删除
removeChildren(el);
}
}
function updateChildren(parentEl, oldCh, newCh) {
let oldStartIdx = 0, newStartIdx = 0;
let oldEndIdx = oldCh.length - 1, newEndIdx = newCh.length - 1;
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
// 双端比较逻辑...
if (sameVnode(oldCh[oldStartIdx], newCh[newStartIdx])) {
patch(oldCh[oldStartIdx], newCh[newStartIdx]);
oldStartIdx++;
newStartIdx++;
} else if (sameVnode(oldCh[oldEndIdx], newCh[newEndIdx])) {
patch(oldCh[oldEndIdx], newCh[newEndIdx]);
oldEndIdx--;
newEndIdx--;
}
// 其他情况处理...
}
}
性能优化策略
- 静态节点提升:编译阶段标记静态节点,Diff 时直接跳过。
- 事件缓存:避免重复绑定事件处理函数。
- 异步更新队列:通过
nextTick批量处理 DOM 更新,减少重排重绘。
与 React Diff 的区别
- 列表策略:Vue 使用双端比较,React 使用单向遍历。
- 组件复用:Vue 通过
key和组件类型匹配复用,React 仅依赖key。 - 移动逻辑:Vue 更擅长处理节点位置变化,React 倾向于按索引顺序更新。







