当前位置: 首页 > news >正文

vue中diff算法的原理

一、Vue 中 Diff 算法的原理与具体过程

Vue 的 虚拟 DOM Diff 算法 是其高效渲染的核心,它通过比较新旧虚拟 DOM(VNode)的差异,计算出最小的 DOM 操作,从而提升性能。以下是 Vue 中 Diff 算法的详细原理和过程。


1. Diff 算法的核心策略

Vue 的 Diff 算法基于 “同层比较”“双端比较” 策略,主要优化点包括:

  1. 同层比较
    • 只比较同一层级的节点,不跨层级移动(复杂度从 O(n³) 降到 O(n))。
    • 如果节点类型不同(如 divspan),直接销毁旧节点,创建新节点。
  2. 双端比较(Four Pointers 算法)
    • 对新旧子节点列表(children)进行 头-头、尾-尾、头-尾、尾-头 的交叉对比,尽量复用 DOM 节点。
    • 通过 key 标识节点,优化列表更新(类似 React,但具体实现略有不同)。
  3. 组件级别优化
    • 如果组件类型相同(如相同的 Vue 组件),复用实例并更新 props/slots
    • 如果组件类型不同,销毁旧组件,挂载新组件。

2. Diff 的具体过程

(1)比较根节点(Patch)

  • 如果新旧节点类型不同(如 divspan):
    直接销毁旧节点及其子节点,创建新节点并插入 DOM。
// 旧 VNode
<div>Hello</div>// 新 VNode
<span>Hello</span>// 操作:删除 div,插入 span
  • 如果节点类型相同(如都是 div):
    1. 更新节点的属性(如 classstyleevent)。
    2. 进入子节点比较(updateChildren)。

(2)比较子节点(UpdateChildren)

Vue 使用 双端指针法 对比新旧子节点列表(oldChildrennewChildren):

  1. 初始化 4 个指针
    • oldStartIdx / oldEndIdx:旧列表的头尾索引。
    • newStartIdx / newEndIdx:新列表的头尾索引。
  2. 循环比较,直到任一列表遍历完成
    • 头-头相同oldStartVNode === newStartVNode):
      复用节点,移动 oldStartIdxnewStartIdx 向右。
    • 尾-尾相同oldEndVNode === newEndVNode):
      复用节点,移动 oldEndIdxnewEndIdx 向左。
    • 头-尾相同oldStartVNode === newEndVNode):
      复用节点,但需要将旧节点移动到列表末尾,移动指针。
    • 尾-头相同oldEndVNode === newStartVNode):
      复用节点,但需要将旧节点移动到列表头部,移动指针。
    • 如果均不匹配
      • 如果有 key,尝试通过 key 查找可复用节点。
      • 如果找不到,创建新节点插入。
  3. 处理剩余节点
    • 如果旧列表先遍历完,将新列表剩余节点插入 DOM。
    • 如果新列表先遍历完,删除旧列表剩余节点。

*� 示例:

// 旧子节点列表
[A, B, C, D]  // oldChildren// 新子节点列表
[D, A, B, E]  // newChildren// Diff 过程:
1.-头(AD)→ 不匹配
2.-尾(DE)→ 不匹配
3.-尾(AE)→ 不匹配
4.-头(D === D)→ 复用 D,移动 D 到头部- 操作:移动 D 到最前面- 指针移动:oldEndIdx--, newStartIdx++5. 新一轮比较:- 旧列表:[A, B, C]- 新列表:[A, B, E]--头(A === A)→ 复用 A--头(B === B)→ 复用 B- 剩余:旧列表 [C],新列表 [E]- 删除 C,插入 E

3. Key 的作用

  • 唯一标识节点:帮助 Diff 算法识别哪些节点可以复用。
  • 错误用法(如用 indexkey):
<!--  不推荐:用 index 当 key -->
<div v-for="(item, index) in list" :key="index">{{ item }}
</div><!--  推荐:用唯一 ID 当 key -->
<div v-for="item in list" :key="item.id">{{ item.name }}
</div>
列表顺序变化时(如排序、插入),会导致错误的复用和性能问题。

4. 总结

  1. Vue Diff 的核心
    • 同层比较 + 双端指针法 + Key 优化。
  2. 具体过程
    • 先比较根节点,再通过双端比较更新子节点列表。
  3. 优化建议
    • 始终为 v-for 设置唯一的 key(不要用 index)。
    • 避免频繁变更节点类型(如 divspan)。

二、Vue Diff 算法完整过程详解

下面我将通过一个完整的 DOM 更新例子,详细讲解 Vue 中 Diff 算法的整个执行过程,包括根节点比较和子节点比较。

示例场景

我们有一个简单的待办事项列表组件,初始状态和更新后的状态如下:

初始状态(旧 VNode)

<div class="todo-list"><h2>待办事项</h2><ul><li key="1">任务A</li><li key="2">任务B</li><li key="3">任务C</li></ul></div>

更新后状态(新 VNode)

<div class="todo-list updated"><h3>我的待办</h3><ul><li key="3">任务C</li><li key="1">任务A</li><li key="4">任务D</li></ul></div>

Diff 算法完整执行过程

第一步:比较根节点

  1. 比较标签名
    • 旧节点:div
    • 新节点:div
    • 结果:相同,继续比较
  2. 比较属性
el.className = "todo-list updated";
 旧属性:`class="todo-list"`新属性:`class="todo-list updated"`操作:更新 class 属性
  1. 标记需要比较子节点
    • 根节点有子节点,进入子节点比较

第二步:比较子节点(第一层)

根节点有两个直接子节点:

  1. 标题(h2 → h3)
  2. 列表(ul)
比较第一个子节点(h2 vs h3)
  1. 比较标签名
    • 旧节点:h2
    • 新节点:h3
    • 结果:不同,执行替换
  2. 操作
const newTitle = document.createElement('h3');
newTitle.textContent = '我的待办';
parentNode.replaceChild(newTitle, oldTitle);
- 创建新 h3 元素
- 设置文本内容为"我的待办"
- 用新 h3 替换旧 h2
比较第二个子节点(ul vs ul)
  1. 比较标签名
    • 相同(都是 ul),继续比较
  2. 比较属性
    • 无属性变化
  3. 比较子节点
    • 进入 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)
第一轮比较
  1. 头头比较(A1 vs C3):
    • key 不同(1 ≠ 3),不匹配
  2. 尾尾比较(C3 vs D4):
    • key 不同(3 ≠ 4),不匹配
  3. 头尾比较(A1 vs D4):
    • key 不同(1 ≠ 4),不匹配
  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)
  1. 头头比较(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)
  1. 头头比较(B2 vs D4):
    • key 不同(2 ≠ 4),不匹配
  2. 尾尾比较(B2 vs D4):
    • key 不同(2 ≠ 4),不匹配
  3. 头尾比较(B2 vs D4):
    • key 不同(2 ≠ 4),不匹配
  4. 尾头比较(B2 vs D4):
    • key 不同(2 ≠ 4),不匹配
  5. 查找 key 映射
    • 在剩余旧节点中查找 key=4 的节点
    • 未找到(只有 B2)
  6. 处理剩余节点

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 class="todo-list updated"><h3>我的待办</h3><ul><li key="3">任务C</li><li key="1">任务A</li><li key="4">任务D</li></ul></div>

Diff 算法总结

  1. 根节点比较
    • 标签名相同 → 比较属性 → 更新 class
    • 子节点不同 → 进入子节点比较
  2. 子节点比较
    • h2 → h3:标签不同,直接替换
    • ul 列表比较:
      • 使用双端指针算法
      • 移动 C3 到开头
      • 保留 A1
      • 删除 B2
      • 新增 D4
  3. DOM 操作汇总
    • 1 次属性更新(div 的 class)
    • 1 次节点替换(h2 → h3)
    • 1 次节点移动(C3 移动到开头)
    • 1 次节点删除(B2)
    • 1 次节点创建和插入(D4)
http://www.lryc.cn/news/571150.html

相关文章:

  • 把springboot打包为maven可引入的jar
  • Maven 的 settings.xml详解
  • 深度学习中常见的激活函数分析
  • Android Studio Jetpack Compose毛玻璃特效按钮
  • 【数据结构】栈和队列详解
  • 线性放大器设计方案:248-双极性任意波1M带宽400Vpp高压线性放大器
  • 欧拉安装vboxlinuxadditions时,出错
  • 第九章——8天Python从入门到精通【itheima】-95~96-Python的异常模块与包(自定义模块并导入+自定义Python包)
  • MySQL 命令行的核心操作命令详解
  • 资深Java工程师的面试题目(一)微服务
  • 如何高效分享WordPress博客文章
  • Sparse4D: 稀疏范式的端对端融合
  • (100)课126: 间隙锁临键锁,是为了锁定索引B+树里叶节点之间的间隙,不允许别的事务在这些间隙里插入新的叶节点,从而阻止幻读
  • 振动测控模块设计原理图:276-16路AD、10路DA嵌入式振动测控模块
  • 自然语言处理文本分类
  • Node.js 项目启动命令全面指南:从入门到精通(术语版)
  • Redis中的分布式锁之SETNX底层实现
  • vue3 watch监视详解
  • 最简单的远程桌面连接方法是什么?系统自带内外网访问实现
  • 基于Cesium的倾斜切片/模型位置方位调整工具
  • Android多媒体——MediaCodec回调与配置(二十五)
  • Android App启动流程深度解析(一):从Activity.startActivity开始
  • 常用交易所链接
  • 【PmHub面试篇】PmHub分布式事务Seata保证任务审批状态一致性面试专题解析
  • fd查找文件并拷贝
  • 【启发式算法】RRT算法详细介绍(Python)
  • OSPF 配置全攻略:从基础原理到实战演练
  • python 重要地位
  • UE/Unity/Webgl云渲染推流网址,如何与外部网页嵌套和交互?
  • 答题考试系统小程序ThinkPHP+UniApp