你了解 Vue 的 diff 算法吗?
Vue 的 diff 算法用于比较新旧虚拟 DOM 树,优化更新操作。该题考察对 Virtual DOM 和渲染性能的理解。
Vue 的 diff 算法主要用于高效比较新旧 Virtual DOM 树的差异,从而最小化 DOM 操作、提升渲染性能。作为面试者,我会从基本原理、工作流程和关键优化点来回答此问题。
📌 基本概念
- Vue 在数据更新时会生成新的 Virtual DOM,并通过 diff 算法(也称为 patch 算法)比较新旧虚拟节点(VNode)树。
- diff 算法操作包含在
patch
函数中,其目标是通过最少量的 DOM 更新渲染视图,而不是重新创建整个 DOM。 - 核心特点:
- 同层级比较:算法只比较处于同一层的同级节点,不会跨层级移动(时间复杂度优化到 O(n))。如果节点类型或 key 不同,直接替换旧节点。
- 双端比较策略:使用头部-尾部指针对比(newStartIdx、newEndIdx vs oldStartIdx、oldEndIdx)从两端向中间逼近(示例见正文)。
🔄 工作流程
diff 算法遵循以下步骤执行:
- 节点同质性判断:通过
sameVnode
函数检查是否可复用旧节点(基于 key、标签名、注释状态等)。function sameVnode(a, b) { return ( a.key === b.key && a.tag === b.tag && a.isComment === b.isComment && !!a.data === !!b.data && sameInputType(a, b) ); }
- 基于 patchChildren 的差异应用:
- 若无 key,则采用简单的
patchUnkeyedChildren
策略: 顺序更新公共长度部分,后追加新增节点或移除多余节点。const patchUnkeyedChildren = (c1: VNode[], c2: VNodeArrayChildren) => { for (let i = 0; i < Math.min(oldLength, newLength); i++) { patch(c1[i], c2[i], container, ...); // 依次更新对应位置节点 } if (oldLength > newLength) { unmountChildren(c1.slice(newLength)); } else { mountChildren(c2.slice(oldLength)); } };
- 若无 key,则采用简单的
- while 循环推进比较(带 key 场景):
- Vue 优先比较相同 key 的可复用节点,避免重排序。
- 循环直到头部指针超过尾部指针为止。
- 更新处理结果:
- 比对结束后,统一更新真实 DOM:添加缺失节点到新位置,或删除旧节点。
- 整个过程被集成在
updateComponent
中,被观察器触发在依赖变更时。
💡 关键优化点
- 同级节点复用:优先检测 key 匹配,减少 DOM 频繁替换带来的性能损耗(基于 key 缓存节点)。
- 节点级优化:为组件级别复用保留实例并更新 props(确保状态一致性)。
- 边界性能提升:与原生 DOM 相比,此过程确保在单帧更新中减少重绘次数(尤其在大量列表更新场景有效)。
此算法显著提高了复杂应用性能,是现代 Vue 的核心设计机制,在大型数据刷新用例中尤为重要。