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

相关推荐
任子菲阳19 小时前
学Java第四十五天——斗地主小游戏创作
java·开发语言·windows
前进的李工19 小时前
LeetCode hot100:234 回文链表:快慢指针巧判回文链表
python·算法·leetcode·链表·快慢指针·回文链表
sin_hielo19 小时前
leetcode 3228
算法·leetcode
百***680419 小时前
Vue项目中 安装及使用Sass(scss)
vue.js·sass·scss
G***T69119 小时前
React性能优化实战,避免不必要的重渲染
前端·javascript·react.js
缪懿19 小时前
JavaEE:多线程基础,多线程的创建和用法
java·开发语言·学习·java-ee
网络点点滴19 小时前
标签的ref属性
前端·javascript·vue.js
Boop_wu19 小时前
[Java EE] 多线程 -- 初阶(2)
java·开发语言·jvm
IT_Beijing_BIT19 小时前
Rust入门
开发语言·后端·rust
青山的青衫20 小时前
【Java基础07】链表
java·开发语言·链表