前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
  • HTTP

    • 基础篇

      • HTTP的前世今生
      • HTTP是什么
      • HTTP世界全览
      • HTTP分层
      • 键入网址到回车发生什么
      • HTTP报文是什么样子的
      • 理解请求方法
      • URI
      • 响应状态码
      • HTTP有哪些特点
      • HTTP优缺点
      • HTTP的实体数据
      • HTTP传输大文件
      • HTTP的连接管理
      • HTTP的重定向
      • HTTP的Cookie机制
      • HTTP的缓存控制
      • HTTP的代理服务
      • HTTP的缓存代理
    • 高级篇

    • 扩展篇

  • 浏览器相关

  • 计算机基础

完整面试题地址:
作者:程序员poetry
扫码关注作者公众号:「前端进阶之旅」 每天分享技术干货
前端进阶之旅公众号二维码

虚拟DOM:虚拟DOM和实际DOM有何不同|浏览器篇

# 虚拟DOM:虚拟DOM和实际DOM有何不同

30 秒速记

  • 虚拟 DOM 是理解 React、Vue 等前端框架底层视图机制的重要切入点。
  • 分析虚拟 DOM 不能脱离浏览器工作机制,需要先明确真实 DOM 的局限。
  • 完整理解路径包括真实 DOM 的问题、虚拟 DOM 的应对方式,以及双缓存和 MVC 两种观察视角。
  • 讨论重点是虚拟 DOM 本身,不等同于完整讲解 React 或 Vue 的全部机制。
  • 原文仅说明 React 和 Vue 使用了虚拟 DOM;涉及具体框架版本与实现差异时,需要另行限定。

虚拟 DOM 是用 JavaScript 对界面结构做的内存描述,真实 DOM 则是浏览器维护并参与渲染的宿主对象。 状态变化后,框架会比较新旧描述,把必要修改集中提交给真实节点,从而减少零散、缺乏组织的操作。比如列表只改一项文本时,最终可以只更新对应文本节点,稳定的 key 还能帮助框架正确复用节点。不过虚拟 DOM 并非天然更快,建树和比较同样有成本,具体收益要结合脚本、布局和绘制耗时来判断。

虚拟 DOM 是最近非常火的技术,两大著名前端框架 React 和 Vue 都使用了虚拟 DOM,所以我觉得非常有必要结合浏览器的工作机制对虚拟 DOM 进行一次分析。当然了,React 和 Vue 框架本身所蕴含的知识点非常多,而且也不是我们专栏的重点,所以在这里我们还是把重心聚焦在虚拟 DOM 上。

在本文我们会先聊聊 DOM 的一些缺陷,然后在此基础上介绍虚拟 DOM 是如何解决这些缺陷的,最后再站在双缓存和 MVC 的视角来聊聊虚拟 DOM。理解了这些会让你对目前的前端框架有一个更加底层的认识,这也有助于你更好地理解这些前端框架。

原理拆解: 真实 DOM 是浏览器维护的宿主对象,节点变化可能使样式计算、布局、绘制或合成失效;业务代码若在一次更新中反复读取布局信息并写入样式,还可能迫使浏览器提前完成布局。虚拟 DOM 则是 JavaScript 中对界面结构的描述。状态变化后,框架生成新的描述,与旧描述比较,再把必要变更集中提交给真实 DOM。它减少的是缺乏组织的宿主操作,并不意味着比较过程或每次更新都没有成本。

具体实现: 假设列表只把第二项文本从 B 改成 C,新的虚拟树可以通过节点类型、属性和子节点关系定位差异,最终只设置对应文本节点。列表插入、删除或重排时,稳定的 key 用于表达节点身份,使框架能够复用正确的真实节点;使用数组下标作为会变化列表的 key,可能把输入框状态复用到错误条目。双缓存视角下,新旧两棵描述树分别承担当前画面和下一画面的角色;MVC 视角下,它位于状态与视图提交之间,把声明式结果转换为命令式修改。

边界与反例: 虚拟 DOM 不是天然快于所有手写 DOM。一个已知位置的单次文本修改,直接更新真实节点通常路径更短;超大列表的全量建树与比较也会消耗 CPU 和内存。性能收益取决于更新范围、调度策略、节点身份以及最终触发的渲染阶段,不能只统计 DOM API 调用次数。不同版本的 React、Vue 在节点表示、调度和优化提示上存在差异,不宜把某一实现当成统一定义。

工程验证: 应使用浏览器性能面板记录脚本执行、样式计算、布局、绘制和长任务,再比较相同交互的提交次数与帧耗时。测试列表时同时检查稳定 key、输入状态和节点复用;出现卡顿时区分是虚拟树计算、真实 DOM 提交,还是布局与绘制成本。只有测量真实用户路径,才能判断优化应落在减少渲染、拆分更新还是缩小可见数据量。

面试官追问

追问 1表单页只需把已知节点的计数从 9 改成 10,同事坚持先构建整棵虚拟树一定比直接设置文本更快,你会如何反驳?
参考回答

已知位置的单次文本修改通常直接更新真实节点路径更短,虚拟树的创建和比较也有成本。虚拟 DOM 的价值在于组织状态到视图的更新并集中提交必要变更,不是保证每次操作都快于手写 DOM。

追问 2聊天页每次收到消息都重建长列表,用户输入开始卡顿,你会要求性能记录里同时观察哪些阶段?
参考回答

应同时观察虚拟树计算、真实 DOM 提交、样式计算、布局、绘制和长任务,不能只统计调用了多少次 DOM API。若主要成本来自超大列表全量建树或不可见节点渲染,优化方向可能是缩小更新范围或可见数据量,而非继续增加比较逻辑。

追问 3可编辑列表支持头部插入,开发把数组下标当作 key,插入后第二行输入框内容跑到别的条目,这与虚拟 DOM 的节点复用有什么关系?
参考回答

变化列表中的下标不能稳定表达条目身份,重排后框架可能把已有真实节点及其输入状态复用给错误数据。应使用能代表业务实体且保持稳定的 key;若数据本身没有稳定身份,仅调整比较策略也难以保证正确复用。

追问 4线上交互掉帧,框架提交日志显示只改了一个节点,开发据此认定虚拟 DOM 没有问题,你还会查什么?
参考回答

只提交一个节点不代表后续渲染成本必然很低,该修改仍可能使样式计算、布局、绘制或合成失效。还要检查业务代码是否交替读取布局信息和写样式,从而迫使浏览器提前布局;结论应来自完整性能轨迹,而非提交数量。

追问 5仪表盘团队在手写精准 DOM 更新与声明式虚拟 DOM 方案之间选型,页面更新范围明确且规模不大,你会怎样权衡?
参考回答

手写已知位置的少量更新可能拥有更短路径,但需要团队自行维护状态与命令式修改的一致性。虚拟 DOM 提供从状态描述到集中提交的组织能力,却引入建树、比较和调度成本;应以真实用户路径测量,而不能只凭框架标签决定。

追问 6面试官要求从双缓存和 MVC 两个角度解释一次列表更新,但不允许把虚拟 DOM 说成浏览器真实节点,你会怎样串联?
参考回答

双缓存视角下,旧描述代表当前画面,新描述代表下一画面,比较后把必要差异提交给真实 DOM。在 MVC 视角中,它位于状态与视图提交之间,把声明式结果转换为命令式修改;它始终是 JavaScript 中的界面描述,而非浏览器宿主节点。

# DOM 的缺陷

30 秒速记

  • 通过 JavaScript 修改真实 DOM 会进入浏览器渲染流程,可能触发样式计算、布局、绘制、栅格化或合成。
  • appendChild、removeChild 等接口改变节点后,影响范围可能超出被操作节点,更新成本取决于实际变更。
  • 不合理地交替读取布局信息和修改结构,可能引发强制同步布局或布局抖动,降低渲染效率。
  • 简单页面的节点规模和更新频率较低,直接操作 DOM 未必形成明显的用户体验问题。
  • 复杂页面和单页应用拥有更大的 DOM 树并持续更新,重复重排或重绘更容易成为性能瓶颈。
  • 引入虚拟 DOM 的直接动机是减少零散的真实 DOM 操作,而不是消除最终的真实节点更新。

真实 DOM 的主要问题,是节点修改可能进入浏览器渲染流程,复杂页面中频繁操作容易带来明显成本。 像 appendChild()、removeChild() 这类操作可能引发样式计算、布局、绘制或合成,交替读取布局信息和写入样式还可能造成强制同步布局。简单页面节点少、更新低频,直接操作 DOM 通常未必有明显问题;但单页应用持续修改大型节点树时,重复重排和重绘更容易成为瓶颈。虚拟 DOM 的作用是组织并减少不必要的真实节点操作,而不是彻底取消真实 DOM 更新。

通过前面一系列文章的学习,你对 DOM 的生成过程应该已经有了比较深刻的理解,并且也知道了通过 JavaScript 操纵 DOM 是会影响到整个渲染流水线的。另外,DOM 还提供了一组 JavaScript 接口用来遍历或者修改节点,这套接口包含了 getElementById、removeChild、appendChild 等方法。

比如,我们可以调用document.body.appendChild(node)往 body 节点上添加一个元素,调用该 API 之后会引发一系列的连锁反应。首先渲染引擎会将 node 节点添加到 body 节点之上,然后触发样式计算、布局、绘制、栅格化、合成等任务,我们把这一过程称为重排。除了重排之外,还有可能引起重绘或者合成操作,形象地理解就是“牵一发而动全身”。另外,对于 DOM 的不当操作还有可能引发强制同步布局和布局抖动的问题,这些操作都会大大降低渲染效率。因此,对于 DOM 的操作我们时刻都需要非常小心谨慎。

当然,对于简单的页面来说,其 DOM 结构还是比较简单的,所以以上这些操作 DOM 的问题并不会对用户体验产生太多影响。但是对于一些复杂的页面或者目前使用非常多的单页应用来说,其 DOM 结构是非常复杂的,而且还需要不断地去修改 DOM 树,每次操作 DOM 渲染引擎都需要进行重排、重绘或者合成等操作,因为 DOM 结构复杂,所生成的页面结构也会很复杂,对于这些复杂的页面,执行一次重排或者重绘操作都是非常耗时的,这就给我们带来了真正的性能问题。

所以我们需要有一种方式来减少 JavaScript 对 DOM 的操作,这时候虚拟 DOM 就上场了

面试官追问

追问 1代码评审时同事说调用一次 appendChild 就必然完整执行样式计算、布局、绘制、栅格化和合成,你会直接认可吗?
参考回答

不能把所有 DOM 修改都机械地等同于完整渲染链路,但向 body 添加节点确实会让渲染引擎更新节点,并可能继续触发样式计算、布局、绘制、栅格化和合成。实际经过哪些阶段取决于变更内容和失效范围,排查时应以性能记录为准。

追问 2一个消息列表收到推送后逐条执行 document.body.appendChild(node),每秒新增数十个节点并出现掉帧,你会怎样改造更新路径?
参考回答

应先减少分散的真实 DOM 写入,把同一批变化收集后再集中提交,避免渲染流水线被频繁启动。还要检查循环中是否夹杂布局读取,因为读写交错可能导致强制同步布局;批量更新会推迟显示,也需要权衡实时性。

追问 3同样是连续追加节点,活动页只有十几个静态元素,而后台工作台已有数千个节点,你会要求两边都引入虚拟 DOM 吗?
参考回答

不会采用同一结论,简单页面的直接 DOM 操作未必对体验产生明显影响,而复杂页面中一次重排或重绘的代价更容易被放大。是否引入虚拟 DOM 应结合结构复杂度和更新频率判断,否则可能为低收益场景增加框架与维护成本。

追问 4线上表格滚动时卡顿,代码在修改行样式后立刻读取尺寸,再继续修改下一行,你最先验证哪类渲染问题?
参考回答

最先验证强制同步布局和布局抖动,因为交替写入样式与读取布局信息可能迫使引擎反复完成布局计算。应在性能面板观察连续的布局任务及其耗时,并缩小触发代码范围;仅看到 DOM API 调用还不足以断定瓶颈。

追问 5团队准备把一个频繁更新的复杂单页应用全部改为手写批量 DOM,另一方主张引入虚拟 DOM,你如何做选型?
参考回答

复杂且持续更新的页面更适合用虚拟 DOM 收集变化并集中应用,以减少无序、重复的真实 DOM 操作。手写批处理可能更直接,却要求团队长期维护更新顺序和一致性;虚拟 DOM 也不是零成本,仍需比较树并最终提交真实节点。

# 什么是虚拟 DOM

← 页面性能:如何系统优化页面PWA:解决了web应用哪些问题 →

fe
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
  • HTTP

    • 基础篇

      • HTTP的前世今生
      • HTTP是什么
      • HTTP世界全览
      • HTTP分层
      • 键入网址到回车发生什么
      • HTTP报文是什么样子的
      • 理解请求方法
      • URI
      • 响应状态码
      • HTTP有哪些特点
      • HTTP优缺点
      • HTTP的实体数据
      • HTTP传输大文件
      • HTTP的连接管理
      • HTTP的重定向
      • HTTP的Cookie机制
      • HTTP的缓存控制
      • HTTP的代理服务
      • HTTP的缓存代理
    • 高级篇

    • 扩展篇

  • 浏览器相关

  • 计算机基础