手机网站 手机app,学做网站学费,济南集团网站建设公司好,重庆市住房和城乡建设厅网站一、Vue 中 Diff 算法的原理与具体过程
Vue 的 虚拟 DOM Diff 算法 是其高效渲染的核心#xff0c;它通过比较新旧虚拟 DOM#xff08;VNode#xff09;的差异#xff0c;计算出最小的 DOM 操作#xff0c;从而提升性能。以下是 Vue 中 Diff 算法的详细原理和过程。 1. D…一、Vue 中 Diff 算法的原理与具体过程
Vue 的 虚拟 DOM Diff 算法 是其高效渲染的核心它通过比较新旧虚拟 DOMVNode的差异计算出最小的 DOM 操作从而提升性能。以下是 Vue 中 Diff 算法的详细原理和过程。 1. Diff 算法的核心策略
Vue 的 Diff 算法基于 “同层比较” 和 “双端比较” 策略主要优化点包括
同层比较 只比较同一层级的节点不跨层级移动复杂度从 O(n³) 降到 O(n)。如果节点类型不同如 div → span直接销毁旧节点创建新节点。 双端比较Four Pointers 算法 对新旧子节点列表children进行 头-头、尾-尾、头-尾、尾-头 的交叉对比尽量复用 DOM 节点。通过 key 标识节点优化列表更新类似 React但具体实现略有不同。 组件级别优化 如果组件类型相同如相同的 Vue 组件复用实例并更新 props/slots。如果组件类型不同销毁旧组件挂载新组件。 2. Diff 的具体过程
1比较根节点Patch
如果新旧节点类型不同如 div → span 直接销毁旧节点及其子节点创建新节点并插入 DOM。
// 旧 VNode
divHello/div// 新 VNode
spanHello/span// 操作删除 div插入 span如果节点类型相同如都是 div 更新节点的属性如 class、style、event。进入子节点比较updateChildren。
2比较子节点UpdateChildren
Vue 使用 双端指针法 对比新旧子节点列表oldChildren 和 newChildren
初始化 4 个指针 oldStartIdx / oldEndIdx旧列表的头尾索引。newStartIdx / newEndIdx新列表的头尾索引。 循环比较直到任一列表遍历完成 头-头相同oldStartVNode newStartVNode 复用节点移动 oldStartIdx 和 newStartIdx 向右。尾-尾相同oldEndVNode newEndVNode 复用节点移动 oldEndIdx 和 newEndIdx 向左。头-尾相同oldStartVNode newEndVNode 复用节点但需要将旧节点移动到列表末尾移动指针。尾-头相同oldEndVNode newStartVNode 复用节点但需要将旧节点移动到列表头部移动指针。如果均不匹配 如果有 key尝试通过 key 查找可复用节点。如果找不到创建新节点插入。 处理剩余节点 如果旧列表先遍历完将新列表剩余节点插入 DOM。如果新列表先遍历完删除旧列表剩余节点。
* 示例
// 旧子节点列表
[A, B, C, D] // oldChildren// 新子节点列表
[D, A, B, E] // newChildren// Diff 过程
1. 头-头A ≠ D→ 不匹配
2. 尾-尾D ≠ E→ 不匹配
3. 头-尾A ≠ E→ 不匹配
4. 尾-头D D→ 复用 D移动 D 到头部- 操作移动 D 到最前面- 指针移动oldEndIdx--, newStartIdx5. 新一轮比较- 旧列表[A, B, C]- 新列表[A, B, E]- 头-头A A→ 复用 A- 头-头B B→ 复用 B- 剩余旧列表 [C]新列表 [E]- 删除 C插入 E3. Key 的作用
唯一标识节点帮助 Diff 算法识别哪些节点可以复用。错误用法如用 index 当 key
!-- 不推荐用 index 当 key --
div v-for(item, index) in list :keyindex{{ item }}
/div!-- 推荐用唯一 ID 当 key --
div v-foritem in list :keyitem.id{{ item.name }}
/div
列表顺序变化时如排序、插入会导致错误的复用和性能问题。4. 总结
Vue Diff 的核心 同层比较 双端指针法 Key 优化。 具体过程 先比较根节点再通过双端比较更新子节点列表。 优化建议 始终为 v-for 设置唯一的 key不要用 index。避免频繁变更节点类型如 div → span。
二、Vue Diff 算法完整过程详解
下面我将通过一个完整的 DOM 更新例子详细讲解 Vue 中 Diff 算法的整个执行过程包括根节点比较和子节点比较。
示例场景
我们有一个简单的待办事项列表组件初始状态和更新后的状态如下
初始状态旧 VNode
div classtodo-listh2待办事项/h2ulli key1任务A/lili key2任务B/lili key3任务C/li/ul/div
更新后状态新 VNode
div classtodo-list updatedh3我的待办/h3ulli key3任务C/lili key1任务A/lili key4任务D/li/ul/div
Diff 算法完整执行过程
第一步比较根节点
比较标签名 旧节点div新节点div结果相同继续比较 比较属性
el.className todo-list updated;旧属性classtodo-list新属性classtodo-list updated操作更新 class 属性标记需要比较子节点 根节点有子节点进入子节点比较
第二步比较子节点第一层
根节点有两个直接子节点
标题h2 → h3列表ul
比较第一个子节点h2 vs h3
比较标签名 旧节点h2新节点h3结果不同执行替换 操作
const newTitle document.createElement(h3);
newTitle.textContent 我的待办;
parentNode.replaceChild(newTitle, oldTitle);- 创建新 h3 元素
- 设置文本内容为我的待办
- 用新 h3 替换旧 h2比较第二个子节点ul vs ul
比较标签名 相同都是 ul继续比较 比较属性 无属性变化 比较子节点 进入 ul 的子节点li 列表比较
第三步比较 ul 的子节点双端指针算法
旧子节点列表
[{ tag: li, key: 1, children: 任务A }, // A1{ tag: li, key: 2, children: 任务B }, // B2{ tag: li, key: 3, children: 任务C } // C3
]新子节点列表
[{ tag: li, key: 3, children: 任务C }, // C3{ tag: li, key: 1, children: 任务A }, // A1{ tag: li, key: 4, children: 任务D } // D4
]初始化指针
oldStartIdx 0 (A1)
oldEndIdx 2 (C3)
newStartIdx 0 (C3)
newEndIdx 2 (D4)第一轮比较
头头比较A1 vs C3 key 不同1 ≠ 3不匹配 尾尾比较C3 vs D4 key 不同3 ≠ 4不匹配 头尾比较A1 vs D4 key 不同1 ≠ 4不匹配 尾头比较C3 vs C3
DOM 操作
ul.insertBefore(li_C, li_A);- key 相同3 3匹配
- 操作* 复用 C3 节点* 将 C3 移动到新位置第一个子节点* 移动指针 oldEndIdx-- → 1 (B2) newStartIdx → 1 (A1)第二轮比较
更新后的指针
oldStartIdx 0 (A1)
oldEndIdx 1 (B2)
newStartIdx 1 (A1)
newEndIdx 2 (D4)头头比较A1 vs A1
DOM 操作无
- key 相同1 1匹配
- 操作* 节点已在正确位置* 移动指针 oldStartIdx → 1 (B2) newStartIdx → 2 (D4)第三轮比较
更新后的指针
oldStartIdx 1 (B2)
oldEndIdx 1 (B2)
newStartIdx 2 (D4)
newEndIdx 2 (D4)头头比较B2 vs D4 key 不同2 ≠ 4不匹配 尾尾比较B2 vs D4 key 不同2 ≠ 4不匹配 头尾比较B2 vs D4 key 不同2 ≠ 4不匹配 尾头比较B2 vs D4 key 不同2 ≠ 4不匹配 查找 key 映射 在剩余旧节点中查找 key4 的节点未找到只有 B2 处理剩余节点
DOM 操作
// 创建并插入 D4
const li_D document.createElement(li);
li_D.textContent 任务D;
ul.appendChild(li_D);// 删除 B2
ul.removeChild(li_B);创建新节点 D4 并插入删除旧节点 B2最终 DOM 结构
div classtodo-list updatedh3我的待办/h3ulli key3任务C/lili key1任务A/lili key4任务D/li/ul/div
Diff 算法总结
根节点比较 标签名相同 → 比较属性 → 更新 class子节点不同 → 进入子节点比较 子节点比较 h2 → h3标签不同直接替换ul 列表比较 使用双端指针算法移动 C3 到开头保留 A1删除 B2新增 D4 DOM 操作汇总 1 次属性更新div 的 class1 次节点替换h2 → h31 次节点移动C3 移动到开头1 次节点删除B21 次节点创建和插入D4