Vue:虚拟Dom

文章目录

  • [一、 Vue 虚拟 DOM (VDOM) 深度解析](#一、 Vue 虚拟 DOM (VDOM) 深度解析)
    • [1. 什么是虚拟DOM](#1. 什么是虚拟DOM)
    • [2. 为什么需要虚拟 DOM?](#2. 为什么需要虚拟 DOM?)
    • [3. VDOM 是如何生成的?](#3. VDOM 是如何生成的?)
    • [4. VDOM 如何做 Diff 算法?](#4. VDOM 如何做 Diff 算法?)
      • [A 核心步骤:Patch 过程](#A 核心步骤:Patch 过程)
      • [B 为什么 key 很重要?](#B 为什么 key 很重要?)
  • [二 、 既然 vue 通过数据劫持可以精确探测数据变化,为什么还需要虚拟 DOM进行 diff 算法?](#二 、 既然 vue 通过数据劫持可以精确探测数据变化,为什么还需要虚拟 DOM进行 diff 算法?)
      • [1. 性能与内存的权衡(颗粒度问题)](#1. 性能与内存的权衡(颗粒度问题))
      • [2. 跨平台能力(Abstraction)](#2. 跨平台能力(Abstraction))
      • [3. 保证更新的"确定性"与批量处理](#3. 保证更新的“确定性”与批量处理)

一、 Vue 虚拟 DOM (VDOM) 深度解析

1. 什么是虚拟DOM

虚拟 DOM 本质上是一个 JavaScript 对象,它用简单的属性来描述真实 DOM 的结构。

Vue 使用虚拟 DOM 主要是为了在 高性能 和 开发体验 之间取得平衡。虽然直接操作原生 DOM 最快,但在复杂的应用中,手动优化每一个 DOM 节点既困难又容易出错。

VNode 结构示例

javascript 复制代码
{
  tag: 'div', // 标签名(如 div, span)或组件名。
  props: { id: 'app' }, // 属性(如 id, class, style)。
  children: [
    { tag: 'p', children: 'Hello World' }
  ], // 子节点(可以是文本,也可以是另一个 VNode 数组)
  key: 123 // 用于优化 diff 的唯一标识
}

2. 为什么需要虚拟 DOM?

很多人误以为 VDOM 比原生 DOM 快,其实这是一个误区。VDOM 的真正价值在于:

  • 减少无谓的重绘与回流: 直接操作 DOM 是很昂贵的。如果你在循环中多次修改 DOM,浏览器会进行多次重排。VDOM 允许 Vue 先在内存中计算好最终的结构,然后一次性"同步"给真实 DOM。

  • 跨平台能力: 因为 VDOM 是一个普通的 JavaScript 对象,它不仅可以映射到浏览器的 DOM,还可以映射到小程序、Weex 或移动端原生组件(iOS/Android)。

  • 声明式编程: 开发者只需要关心数据的状态(State),而不需要手动去写 appendChild 或 removeChild。Vue 会自动通过 VDOM 找出状态变化前后的差异并更新。


3. VDOM 是如何生成的?

在 Vue 中,VDOM 是通过 渲染函数(Render Function) 生成的。

  1. 模板编译 : 你写的 <template>标签会被 Vue 的编译器转换成 render 函数。

  2. 执行渲染函数: 当组件需要渲染或数据发生变化时,Vue 会执行这个 render 函数。

  3. 产生 VNode: render 函数内部会调用类似 h() (hyperscript) 的方法。这个函数会返回一个纯 JavaScript 对象,这就是 VNode(虚拟节点)。

4. VDOM 如何做 Diff 算法?

vue 的 Diff 算法遵循 "深度优先、同层比较" 的策略。

  • 深度优先 (Depth-First Search, DFS): 当比较两棵虚拟 DOM 树时,算法会先沿着一个节点往下走,直到最底层的子节点,然后再回过头处理旁边的兄弟节点。

    • 执行逻辑:如果发现 <div> 标签没变,它不会立刻去看 <div> 后面的 <p> 标签,而是先钻进 <div> 内部去对比它的 children(子节点)。

    • 为什么要这么做? DOM 是树状结构。为了确保父节点及其包含的所有子节点都能正确更新,Vue 会递归地遍历每一个分支,确保局部结构的完整性。

  • 同层比较:算法只会对 同一层级 的节点进行比较,而不会跨层级寻找。

    • 核心规则:如果旧树的第三层有一个 <span>,新树把它移到了第二层,Vue 不会去尝试复用这个 。它会直接销毁旧的 ,并在新位置创建一个新的。
    • 为什么不跨层找? 在 Web 开发中,DOM 节点的跨层级移动其实非常罕见。如果要进行全量对比(即 O ( n 3 ) O(n^3) O(n3) 复杂度的算法),性能消耗巨大。Vue 通过"同层比较"将复杂度降低到了 O ( n ) O(n) O(n),极大地提升了速度。

A 核心步骤:Patch 过程

当数据变化导致生成了新的 VNode 时,Vue 会将新旧两棵 VNode 树进行对比:

  • 同层比较: 如果新旧节点的父节点不同,直接销毁旧的,创建新的。

  • 判断是否为相同节点 : Vue 通过 tag 和 key 来判断两个节点是否是"同一个"。如果不同,直接替换。

  • 对比子节点 (Children): 这是 Diff 的精华所在

    • Vue 2:双端 Diff 算法

    • Vue 3:快速 Diff 算法 (Quick Diff)

Vue2的 双端 diff算法 与 Vue3 的 快速diff 算法

B 为什么 key 很重要?

在 Diff 过程中,key 是节点的唯一身份证。有了 key,Vue 就能准确知道某个节点只是移动了位置,而不是被删除了,从而避免不必要的 DOM 重新创建,极大提升了列表渲染的性能。

二 、 既然 vue 通过数据劫持可以精确探测数据变化,为什么还需要虚拟 DOM进行 diff 算法?

简单来说:数据劫持(Proxy/Object.defineProperty)能让你知道"谁变了",但不能高效地告诉你"怎么变最省钱"。

我们可以从以下三个维度来构建面试答案:

1. 性能与内存的权衡(颗粒度问题)

Vue 的响应式系统确实可以做到"极致精确",比如给每个 HTML 标签甚至每个文本节点都绑定一个 Watcher/Effect。但这样做会有严重的副作用:

复制代码
- **内存开销巨大**:如果页面有 10,000 个动态节点,就会有 10,000 个 Effect 对象。在大型应用中,这会吃掉大量内存。

- **依赖追踪的成本**:每个节点都去收集依赖、建立映射关系,初始化的耗时会非常长。

Vue 的方案:采取了中等颗粒度

  • 每个组件对应一个 Watcher/Effect。当组件内的数据变了,Vue 只知道"这个组件需要重新渲染",但不知道组件内部到底是哪一个 <div> 变了。
  • 这时候,就需要 虚拟 DOM 和 Diff 算法 来找出组件内部最小的更新范围。

2. 跨平台能力(Abstraction)

虚拟 DOM 是对真实 UI 的一层抽象。

  • 如果 Vue 直接操作真实 DOM,那么它就只能运行在浏览器里。

  • 有了虚拟 DOM,渲染函数产出的是一个 JavaScript 对象。这个对象可以被渲染成浏览器里的 DOM,也可以通过不同的渲染器(Renderer)变成 iOS/Android 的 原生控件(如 Weex),甚至是 Canvas 或 SSR(服务端字符串)。

如果没有虚拟 DOM 这一层中间层,Vue 的跨平台扩展性会大打折扣。

3. 保证更新的"确定性"与批量处理

在复杂的业务逻辑中,你可能会在一行代码里连续修改 5 个状态变量。

  • 如果没有虚拟 DOM:可能触发 5 次真实的 DOM 操作,引发 5 次严重的浏览器重排(Reflow)。

有了虚拟 DOM:

  • 5 次修改触发 1 次组件 Effect 运行(异步队列机制)。

  • 渲染函数生成一套新的虚拟 DOM 树。

  • Diff 算法 在内存里把这 5 次修改合并,最终计算出一次最精简的 DOM 指令。

总结:

  • "Vue 之所以保留虚拟 DOM,是为了在响应式追踪的内存开销和DOM 操作的执行效率之间寻找最优解。

  • 响应式系统解决了 '何时更新'(Scheduling)的问题,而虚拟 DOM 和 Diff 算法解决了'如何最小化更新'(Rendering) 的问题。

  • 到了 Vue 3,通过 Compiler-Informed Virtual DOM(带有编译时信息的虚拟 DOM),Vue 进一步在 Diff 阶段利用 静态标记(Patch Flags) 跳过了不动的节点,让 Diff 的性能趋近于原生操作。"

相关推荐
vivo互联网技术2 小时前
下一代图片格式 AVIF 在 vivo 社区的落地实践
前端·性能优化·图片压缩·avif
咸鱼翻身更入味2 小时前
Vue创建一个简单的Agent聊天
前端
bluetata2 小时前
AI 浪潮与破局:TypeScript 生态实战,让 AI 为你所用
javascript·人工智能·typescript
布局呆星2 小时前
Vue Router 核心知识点梳理
前端·javascript·vue.js
得物技术2 小时前
基于 Harness + SDD + 多仓管理模式的 AI 全栈开发实践|得物技术
前端·人工智能·后端
不会写DN2 小时前
如何通过 Python 实现招聘平台自动投递
开发语言·前端·python
JiaWen技术圈2 小时前
增量静态再生(ISR)详解:Next.js 中的实现与应用
javascript·git·ubuntu
miaowmiaow2 小时前
一行命令把 PSD 还原成 HTML / React / Vue:psd2code 实战干货
前端·ai编程