【Vue2 ✨】Vue2 入门之旅 · 进阶篇(二):虚拟 DOM 与 Diff 算法

在上一篇中,我们了解了 Vue2 的响应式原理,它实现了数据变化自动驱动视图更新。那么,当数据变化后,Vue2 是如何高效地更新真实 DOM 的呢?本篇将带你深入理解 Vue2 的 虚拟 DOMDiff 算法


目录

  1. [什么是虚拟 DOM](#什么是虚拟 DOM)
  2. [为什么需要虚拟 DOM](#为什么需要虚拟 DOM)
  3. [Vue2 中的虚拟 DOM](#Vue2 中的虚拟 DOM)
  4. [Diff 算法](#Diff 算法)
  5. 小结

什么是虚拟 DOM

虚拟 DOM (Virtual DOM) 是用 JavaScript 对象来表示真实 DOM 的一种方式。 它本质上是真实 DOM树的一个"轻量级"副本,通过 JS 对象描述 UI 结构,而非直接操作浏览器 DOM。 虚拟 DOM 的作用是:当数据变化时,先在内存中比较虚拟 DOM 的差异(Diff),再将最小化的变更应用到真实 DOM 上,从而避免频繁的直接 DOM操作。

例子:一个简单的虚拟 DOM 表示一个 div 元素,包含一个 p 子元素:

html 复制代码
<div id="app">
  <p>Hello World</p>
</div>

对应的虚拟 DOM 对象:

js 复制代码
{
  tag: 'div',
  data: { id: 'app' },
  children: [
    {
      tag: 'p',
      children: [{ text: 'Hello World' }]
    }
  ]
}

通过这种对象表示,Vue 可以快速创建、比较和更新 UI,而无需立即触及昂贵的真实 DOM 操作。


为什么需要虚拟 DOM

直接操作真实 DOM 成本很高,因为浏览器需要重新计算布局、样式和重绘页面。每次小变化都可能导致全量重绘,性能低下。

虚拟 DOM 的引入解决了这些问题:

  • 批量、最小化更新:数据变化时,先在 JS 层面计算差异,只更新必要的真实 DOM 部分。
  • 提升跨平台能力:虚拟 DOM 不依赖浏览器,可以用于 Native 应用、服务器端渲染 (SSR) 等场景。
  • 抽象层:开发者无需关心底层 DOM 差异,框架统一处理。

在 Vue2 中,虚拟 DOM 是响应式系统与视图更新的桥梁:响应式检测到数据变化后,生成新的虚拟 DOM 树,然后通过 Diff 算法高效 patch 到真实 DOM。


Vue2 中的虚拟 DOM

在 Vue2 中,虚拟 DOM 节点 (VNode) 是一个普通的 JS 对象,包含标签、属性、子节点等信息。

Vue 会将模板编译成渲染函数,这个函数返回虚拟 DOM 树。每个 VNode 对象结构大致如下:

js 复制代码
const vnode = {
  tag: 'div',          // 标签名
  data: {              // 属性和数据
    id: 'app',
    class: 'container'
  },
  children: [          // 子节点数组
    {
      tag: 'p',
      text: 'Hello Vue!',  // 文本节点
      children: []
    }
  ],
  text: undefined      // 如果是纯文本节点
}

当组件初始化或数据更新时,Vue 调用渲染函数生成新的 VNode 树,与旧树进行比较。

这种对象表示轻量高效,便于 JS 引擎优化。


Diff 算法

Diff 算法是虚拟 DOM 的核心,用于比较新旧 VNode 树,找出最小变更集。Vue2 的 Diff 算法采用 同层比较策略,只在同一层级进行比较,不会跨层级移动节点,从而简化计算并提升性能。

核心原则:

  • 同层比较:只比较当前层级的节点,不会跨层级对比(例如,不会将 div 的子元素与 span 的子元素比较)。
  • 通过 key 标识优化:使用 key 属性标识节点,实现精准复用,避免不必要的创建/删除。

伪代码示例(简化版):

js 复制代码
function diff(oldVNode, newVNode) {
  if (oldVNode.tag !== newVNode.tag) {
    // 标签不同,直接替换整个节点
    replaceNode(oldVNode, newVNode)
  } else {
    // 标签相同,比较属性和子节点
    patchProps(oldVNode.data, newVNode.data)
    diffChildren(oldVNode.children, newVNode.children)
  }
}

function diffChildren(oldChildren, newChildren) {
  // 遍历新旧子节点列表
  for (let i = 0; i < newChildren.length; i++) {
    let oldChild = findMatchingChild(oldChildren, newChildren[i], i)
    if (oldChild && oldChild.key === newChildren[i].key) {
      // key 相同,复用节点并递归 Diff
      diff(oldChild, newChildren[i])
    } else {
      // 不同,创建新节点或删除旧节点
      createNode(newChildren[i])
    }
  }
}

在实际实现中,Vue2 使用双端指针(head-tail 比较)优化列表 Diff,时间复杂度为 O(n),远优于全量重建的 O(n³)。

注意:key 的正确使用至关重要,它帮助 Diff 算法准确识别节点,避免列表重排序时的性能问题。


小结

通过引入虚拟 DOM 和 Diff 算法,Vue2 成功地优化了视图更新过程,减少了不必要的真实 DOM 操作,从而提高了性能。虚拟 DOM 提供了一个高效、跨平台的方式来处理 UI 更新,而 Diff 算法则确保了最小化的变更集被应用到真实 DOM 中。


📗 下一篇进阶文章,我们将学习 模板编译原理,理解 Vue 如何将模板编译成虚拟 DOM。

相关推荐
未知陨落6 分钟前
LeetCode:81.爬楼梯
算法·leetcode
SHtop1125 分钟前
排序算法(golang实现)
算法·golang·排序算法
ftpeak33 分钟前
《WebAssembly指南》第九章:WebAssembly 导入全局字符串常量
开发语言·rust·wasm
卡戎-caryon1 小时前
【Java SE】06. 数组
java·开发语言
Rain_is_bad1 小时前
初识c语言————数学库函数
c语言·开发语言·算法
lsx2024061 小时前
Eclipse 快捷键
开发语言
艾醒2 小时前
大模型面试题剖析:模型微调中冷启动与热启动的概念、阶段与实例解析
深度学习·算法
木易 士心2 小时前
Ref 和 Reactive 响应式原理剖析与代码实现
前端·javascript·vue.js
数字化顾问2 小时前
从索引失效到毫秒级响应——SQL 优化实战案例:从慢查询到高性能的完整指南之电商大促篇
java·开发语言·数据库
被巨款砸中3 小时前
一篇文章讲清Prompt、Agent、MCP、Function Calling
前端·vue.js·人工智能·web