当前位置:首页 > 技术 > 正文内容

深入剖析 Vue 虚拟 DOM 构建与差异更新策略

访客 技术 2026年7月4日 1

一、核心概念:VNode 与 Virtual DOM

在框架内部,我们通常不会直接操作浏览器提供的真实 DOM API,而是通过一层抽象层进行交互。

  • VNode(Virtual Node):本质是 JavaScript 对象。它通过特定的属性结构来描述一个 DOM 节点的形态,包括标签类型、属性配置以及子节点信息。
  • Virtual DOM(VDOM):由多个 VNode 实例嵌套组成的树状数据结构。它是真实 DOM 在内存中的轻量级映射。

当数据状态发生变更时,渲染函数(render)会被重新执行,生成一棵新的 VNode 树(nextVNode)。随后,框架会对比上一帧的旧树(prevVNode)与新树,通过差异计算得出最小变更集,最后将这些变更应用到真实 DOM 上。

二、引入虚拟 DOM 的技术动因

直接使用原生 DOM API 存在显著的效能瓶颈:

  • 性能开销大:频繁触发重绘(Repaint)与回流(Reflow),阻塞主线程,导致页面卡顿。
  • 逻辑复杂:手动维护复杂的 UI 状态和 DOM 同步极易出错,特别是在大型应用场景下。

虚拟 DOM 的核心价值在于利用 JavaScript 的高运算速度模拟 DOM 结构。通过算法计算出新旧版本之间的最小差异路径,将 DOM 操作限制在必要的范围内,从而显著提升渲染效率。

三、VNode 的底层数据结构

假设有一段 HTML 结构如下:

<div id="main" class="wrapper">
    <p>标题</p>
    <ul style="color: #333">
        <li>项目 A</li>
    </ul>
</div>

在框架内部,这会被编译并转换为如下的 JavaScript 对象结构:

{
  type: 'DIV',
  attributes: { id: 'main', className: 'wrapper' },
  children: [
    {
      type: 'P',
      text: '标题'
    },
    {
      type: 'UL',
      attributes: { style: 'color: #333' },
      children: [
        {
          type: 'LI',
          text: '项目 A'
        }
      ]
    }
  ]
}

在 Vue 的实际运行环境中,挂载阶段调用的 render 方法生成的节点包含更多元数据,例如:tag(标签名)、data(事件与样式数据)、children(子列表)、text(文本内容)、elm(指向真实 DOM 的引用)以及至关重要的 key(唯一标识符,用于优化更新匹配)。

四、Diff 算法的必要性与复杂度优化

为了保持视图与数据的一致性,必须比较两次渲染产生的树形结构。如果采用最朴素的遍历全树比对方案,时间复杂度高达 O(n³)。对于拥有上千个节点的应用,这将产生亿级的运算量,显然是不可接受的。

Vue 的优化目标是将复杂度降低至接近 O(n),其核心策略包括:

  1. 层级对比:仅在同一深度层级进行比较,不进行跨层查找。
  2. 节点判定:若标签名(tag)不同或 Key 值不匹配,直接视为新节点,丢弃旧节点重建。
  3. 复用判断:若 Tag 与 Key 均相同,则认为是同一节点,仅检查属性和内容的变化。

五、差异化比对核心流程

Diff 过程发生在视图更新周期中,旨在以最小代价修正视图。主要遵循以下步骤:

  1. 同级遍历:递归遍历树的每一层,保证整体线性复杂度。
  2. 类型检查:优先判断标签类型。若类型不一致,直接执行替换操作(删除旧 DOM,插入新 DOM)。
  3. 身份验证:若类型一致,进一步校验 key 属性。Key 不同意味着节点身份改变,同样执行替换;Key 相同则进入属性与子节点的细粒度比对。
  4. 属性修补(Patch):针对相同的节点,遍历属性差异并更新真实 DOM。处理完当前节点后,继续下沉至子节点数组进行比对。
  5. 子节点同步:这是算法的核心部分。当父节点被确认为相同后,需要对比其子节点列表的变化。

六、子节点更新策略详解

在 Vue 的实现中,这个过程主要由 patchupdateChildren 函数协同完成。我们可以将其理解为一个高效的树比对器。

1. Patch 主干逻辑

当接收到新旧两个根节点时,系统首先进行基础校验:

  • 若新节点为空而旧节点存在,说明组件销毁,触发卸载钩子。
  • 若旧节点为真实 DOM 元素(初始挂载),直接创建新 DOM。
  • 若两者均为 VNode,调用相似度检测函数(如 sameNode)。

节点相同的判定条件通常包括:key 一致、tag 一致、输入类型一致等。若不满足,直接替换;若满足,则调用 patchVnode 进入深层比对。

2. 子节点双端指针算法

对于子节点数组的比对,框架采用了四个指针策略,分别指向旧数组的头尾和新数组的头尾(例如:prevStart, prevEnd, nextStart, nextEnd)。通过循环推进指针,减少不必要的移动:

  • 头部对齐:比较 prevStartnextStart。若相同,执行更新并向后移动指针。
  • 尾部对齐:比较 prevEndnextEnd。若相同,执行更新并向前移动指针。
  • 交叉移动:
    • prevStart 等于 nextEnd,说明节点右移了。移动 DOM 元素位置,调整指针。
    • prevEnd 等于 nextStart,说明节点左移了。移动 DOM 元素位置,调整指针。
  • 哈希查找:若上述四种情况均未命中,使用 nextStart 的 Key 在旧节点列表中检索。
    • 未找到:说明是新增节点,创建 DOM 并插入。
    • 已找到:说明是移动过的节点。复用旧 DOM,将其移动到当前位置,并将旧列表对应位置标记为空。

该循环持续进行,直到任一数组的起始索引超过结束索引。循环结束后,根据剩余情况处理剩余的插入或删除逻辑,最终完成整棵树的视图同步。

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。