尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

虚拟 DOM 与 Diff 算法

虚拟 DOM 与 Diff 算法
📅 发布时间:2026/6/27 0:04:57

文章目录

  • 前言
  • 一、什么是虚拟 DOM
    • 1.1 定义
    • 1.2 为什么需要
    • 1.3 并非总是更快
  • 二、VNode 结构
    • 2.1 基本字段
    • 2.2 常见类型
  • 三、更新流程
  • 四、Vue 2 双端 Diff
    • 4.1 算法思路
    • 4.2 特点
  • 五、Vue 3 快速 Diff
    • 5.1 为什么放弃双端 Diff
    • 5.2 快速 Diff 流程(列表)
    • 5.3 最长递增子序列(LIS)
  • 六、Vue 3 编译时优化(概览)
    • 6.1 PatchFlag:标记动态节点
    • 6.2 Block Tree:扁平化动态节点
    • 6.3 静态提升
  • 七、key 与 Diff 的关系(简述)
  • 八、与 React 的对比
  • 九、面试聚焦
    • 9.1 虚拟 DOM 并非总是更快
    • 9.2 Vue 3 为什么改 Diff?
    • 9.3 PatchFlag 做什么?
    • 9.4 Block Tree 是什么?
  • 十、易混淆点
  • 十一、思考与练习
  • 总结

前言

虚拟 DOM 是 Vue 渲染层的核心机制:用 JavaScript 对象描述 DOM,通过 Diff 算法找出最小变更并批量更新。本篇会讲清楚:

  • VNode 结构与更新流程
  • Vue 2 双端 Diff vs Vue 3 快速 Diff
  • 最长递增子序列(LIS)与编译时优化概览

一、什么是虚拟 DOM

1.1 定义

虚拟 DOM 是用 JavaScript 对象描述真实 DOM 结构的轻量级表示。状态变化时生成新的 VNode 树,与旧树 Diff 后,只把差异应用到真实 DOM。

// 真实 DOM<divclass="box"><h1>Hello</h1></div>// 虚拟 DOM(VNode 示意)constvnode={type:'div',props:{class:'box'},children:[{type:'h1',children:'Hello'}]}

1.2 为什么需要

问题虚拟 DOM 的解决
直接操作 DOM 慢且难追踪声明式描述 UI,框架算最优更新
多次状态变更多次 DOM 操作合并为一次批量更新
跨平台(Web / SSR / 小程序)同一套 VNode 可对接不同渲染器

1.3 并非总是更快

极少量的 DOM 更新时,手动改 DOM 可能更快。虚拟 DOM 的价值在于复杂场景下的声明式开发、批量更新和跨平台抽象,而不是「一定比原生 DOM 快」。


二、VNode 结构

2.1 基本字段

constvnode={type:'div',// 标签名、组件、Fragment 等props:{class:'box'},children:[],key:'unique-id',// 列表 Diff 用el:null,// 运行时关联的真实 DOMpatchFlag:0,// Vue 3 编译时标记(见下文)dynamicChildren:null// Vue 3 Block Tree 动态子节点}

2.2 常见类型

// 文本{type:Text,children:'hello'}// 元素{type:'div',props:{},children:[]}// 组件{type:MyComponent,props:{msg:'hi'}}// Fragment(多根节点){type:Fragment,children:[...]}

三、更新流程

响应式数据变化 ↓ 触发组件 re-render,生成新 VNode 树 ↓ 新旧 VNode 树 Diff(patch) ↓ 计算出最小变更集(增删改移) ↓ 批量应用到真实 DOM
// 简化 Diff 思路functionpatch(oldVNode,newVNode){if(oldVNode.type!==newVNode.type){// 类型不同 → 替换节点replaceNode(oldVNode,newVNode)return}// 同类型 → 比 props、比 childrenpatchProps(oldVNode,newVNode)patchChildren(oldVNode,newVNode)}

四、Vue 2 双端 Diff

4.1 算法思路

Vue 2 对同级列表使用双端比较:新旧数组各设头尾指针,从两端向中间同时比较,尽量复用 DOM。

旧: A B C D ↑ ↑ oldStart oldEnd 新: D A B C ↑ ↑ newStart newEnd 比较顺序(共 4 种): 1. oldStart vs newStart 2. oldEnd vs newEnd 3. oldStart vs newEnd 4. oldEnd vs newStart 都不匹配 → 用 key 在旧列表中查找

4.2 特点

  • 适合列表头尾增删、反转等常见场景
  • 依赖key做节点身份识别
  • 最坏情况仍需较多比较,Vue 3 做了进一步优化

五、Vue 3 快速 Diff

5.1 为什么放弃双端 Diff

Vue 3 借鉴 Inferno 的快速 Diff,目标:

  • 减少不必要的节点比较次数
  • 用最长递增子序列(LIS)优化列表移动操作,少做 DOM insert/move

5.2 快速 Diff 流程(列表)

1. 从头同步:新旧节点 type + key 相同则 patch,不同则停 2. 从尾同步:同上,从尾部向前 3. 中间段:用 key → index 映射处理新增、删除、移动 4. 移动优化:对需要移动的节点求 LIS,LIS 内节点不移动,其余按需 insert

5.3 最长递增子序列(LIS)

旧: [A, B, C, D, E] 新: [A, C, D, B, E] (B 从 index 1 移到 index 3) 需要移动的: B LIS 帮助找出「已经相对有序、不必动」的节点,减少 DOM 移动次数

LIS 让 Diff 在「乱序但可复用」的列表里,用最少 DOM 移动完成更新。


六、Vue 3 编译时优化(概览)

Diff 之外,Vue 3 在编译阶段减少需要 Diff 的节点量:

6.1 PatchFlag:标记动态节点

编译器分析模板,给 VNode 打上「哪里会变」的标记:

// 编译结果示意createElementVNode('div',{class:'static'},createTextVNode('hello',PatchFlags.TEXT)// 仅文本会变)
PatchFlag含义
TEXT (1)动态文本
CLASS (2)动态 class
STYLE (4)动态 style
PROPS (8)动态 props
…组合标记

Diff 时若 patchFlag 为 0,可跳过该节点子树比较;有标记则只比较标记部分。

6.2 Block Tree:扁平化动态节点

将模板中的动态节点收集到dynamicChildren数组,Diff 时只遍历动态节点,静态子树整段跳过。

<div> <p>静态标题</p> <!-- 静态,不参与 Diff --> <p>{{ msg }}</p> <!-- 动态,进入 dynamicChildren --> <span>{{ count }}</span> <!-- 动态,进入 dynamicChildren --> </div>

6.3 静态提升

纯静态节点提升到 render 函数外,只创建一次,后续 render 直接复用,避免重复生成 VNode。

编译优化的细节(PatchFlag 类型、Block 收集规则等)在编译优化专题中展开。


七、key 与 Diff 的关系(简述)

列表 Diff 依赖key判断「同一节点」:

  • 相同 key + 相同 type → patch(复用 DOM)
  • 不同 key → 销毁旧节点,创建新节点

key 不稳定(如用 index 做增删)会导致错误复用。key 的完整原理见专题「Key 的作用与原理」。


八、与 React 的对比

对比项Vue 3React
Diff 粒度组件级 + Block 内动态节点Fiber 可中断的增量 Diff
列表算法快速 Diff + LIS单端 + key 映射
编译优化PatchFlag、静态提升、Block Tree部分优化,策略不同

两者都用虚拟 DOM,但 Diff 策略和编译优化路径不同。


九、面试聚焦

9.1 虚拟 DOM 并非总是更快

简单场景手动 DOM 可能更快;虚拟 DOM 的价值是声明式、批量更新、跨平台。

9.2 Vue 3 为什么改 Diff?

双端 Diff 在复杂列表下比较次数仍偏多;快速 Diff + LIS 减少移动,配合 PatchFlag / Block Tree 减少比较范围。

9.3 PatchFlag 做什么?

编译期标记 VNode 哪些部分动态,Diff 时跳过静态内容,只更新标记位。

9.4 Block Tree 是什么?

把动态节点扁平收集,Diff 只比 dynamicChildren,静态子树整段跳过。


十、易混淆点

  1. 虚拟 DOM ≠ 更快:是开发模型和批量更新策略,不是性能银弹。
  2. Diff 只做同级比较:不会跨层级移动节点(O(n) 层级比较)。
  3. Vue 2 / Vue 3 列表 Diff 不同:Vue 3 用快速 Diff + LIS。
  4. PatchFlag 是编译产物:手写 render 函数默认无此优化。
  5. Shadow DOM ≠ Virtual DOM:前者是 Web Components 浏览器封装,与框架 VNode 无关。

十一、思考与练习

1.虚拟 DOM 的更新流程是什么?

解析:数据变 → 新 VNode → 与旧 VNode Diff → 最小变更 → 更新真实 DOM。

2.Vue 2 和 Vue 3 列表 Diff 有何不同?

解析:Vue 2 双端比较;Vue 3 快速 Diff,头尾同步 + 中间 key 映射 + LIS 优化移动。

3.LIS 在 Diff 中的作用?

解析:找出相对有序、不必移动的节点,减少 DOM insert/move 次数。

4.PatchFlag 和 Block Tree 解决什么问题?

解析:减少 Diff 范围——只比动态节点、只更新标记为动态的部分,静态内容跳过。

5.为什么说虚拟 DOM 不总是更快?

解析:创建 VNode 和 Diff 本身有开销;极简单更新直接改 DOM 可能更省。


总结

  • 虚拟 DOM:JS 对象描述 DOM,声明式 + 批量更新 + 跨平台
  • 更新流程:新 VNode → Diff → 最小 patch → 真实 DOM
  • Vue 2:双端 Diff,依赖 key
  • Vue 3:快速 Diff + LIS;PatchFlag、Block Tree、静态提升减少 Diff 量
  • 本质:虚拟 DOM 是工程权衡,不是「一定比原生 DOM 快」

相关新闻

  • 如何快速解锁QQ音乐加密文件:qmcdump完整解密教程
  • 5分钟玩转DLSS版本管理:DLSS Swapper让你的游戏性能自由切换
  • 现场 w3wp 卡顿,dump 抓回来托管栈全是死的:一次从 696 万对象里挖根因的排查实录

最新新闻

  • Type-C一拖多快充线:智能功率分配与选购指南
  • 94个公共Tracker服务器:彻底终结BT下载卡在99%的终极解决方案
  • 生产环境下的Agent记忆机制设计:短期上下文与长期向量库的工程化取舍
  • 硬件预取器安全挑战与PhantomFetch防御技术解析
  • 基于4G和GPS的智慧养殖物联网终端设计与优化
  • 前端XSS攻击防御实战:从原理到2025年立体化安全方案

日新闻

  • 单节点跑业务稳如泰山 扩容高可用集群反而频繁卡死 复盘完整连接交互揪出深层根因
  • Boss直聘批量投递工具:5倍效率提升的求职价值重构指南
  • 3分钟解锁VLC点击暂停插件:让视频控制变得如此简单!

周新闻

  • Visual C++运行库修复终极指南:5分钟快速解决Windows软件启动错误
  • 手把手教你构建统计局地区经济数据爬虫:从环境搭建到数据持久化全指南
  • 2026多Agent深度解析:用AI团队替代单一模型,四种架构实战落地

月新闻

  • 【总结】入门篇:50句话让你记住架构核心概念
  • WeChatMsg技术方案解析:实现Mac微信数据自主管理的完整解决方案
  • WeChatMsg:革新性微信数据备份方案,打造你的专属数字记忆库

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号