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 的性能趋近于原生操作。"

相关推荐
JeffongTan1 小时前
在LWC中镶嵌VF Page获取用户IP
前端·javascript·salesforce
不cong明的亚子3 小时前
web live多屏互动平台
javascript·经验分享·音视频
陆枫Larry3 小时前
Astro 是什么
前端
是立不是利5 小时前
写给设计师的 CSS 博客美学指南
前端·css
dsyyyyy11015 小时前
JS复习,难点,易错点总结
开发语言·javascript·ecmascript
默_笙6 小时前
🏝 Docker 就是"房地产开发":从施工图纸到小区物业的容器化指南
前端·javascript
计算机魔术师6 小时前
Rohan Paul 谈用时间跨度衡量智能体能力,并引用 OpenAI 自动化研究实习生里程碑
前端
kyriewen6 小时前
我手写了个极简版 React Router——才搞懂 v6 为什么砍了这么多 API
前端·javascript·程序员
a1117766 小时前
Shirone 二次元博客主题 开源
前端·开源
IT_陈寒6 小时前
Python的切片赋值把我坑惨了,这不是bug是特性
前端·人工智能·后端