【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。

相关推荐
和光同尘 、Y_____2 小时前
BRepMesh_IncrementalMesh 重构生效问题
c++·算法·图形渲染
Hexene...3 小时前
【前端Vue】el-dialog关闭后黑色遮罩依然存在如何解决?
前端·javascript·vue.js·elementui·前端框架
Jay_See3 小时前
JC链客云——项目过程中获得的知识、遇到的问题及解决
前端·javascript·vue.js
sali-tec3 小时前
C# 基于halcon的视觉工作流-章32-线线测量
开发语言·人工智能·算法·计算机视觉·c#
lingran__3 小时前
速通ACM省铜第一天 赋源码(The Cunning Seller (hard version))
c++·算法
草字3 小时前
css flex布局,设置flex-wrap:wrap换行后,如何保证子节点被内容撑高后,每一行的子节点高度一致。
前端·javascript·css
塔中妖3 小时前
【华为OD】数字游戏
算法·游戏·华为od
little_xianzhong3 小时前
Java 日期字符串万能解析工具类(支持多种日期格式智能转换)
java·开发语言
pzx_0013 小时前
【LeetCode】392.判断子序列
算法·leetcode·职场和发展