1. 虚拟DOM是什么?

虚拟 DOM(Virtual DOM)本质上是JS 和 DOM 之间的一个映射缓存,它在形态上表现为一个能够描述 DOM 结构及其属性信息的 JS 对象。

  • 虚拟 DOM 是 JS 对象
  • 虚拟 DOM 是对真实 DOM 的描述

2. 虚拟DOM大致工作流程

  • 挂载阶段,React 将结合 JSX 的描述,构建出虚拟 DOM 树,然后通过 ReactDOM.render 实现虚拟 DOM 到真实 DOM 的映射(触发渲染流水线);
  • 更新阶段,页面的变化在作用于真实 DOM 之前,会先作用于虚拟 DOM,虚拟 DOM 将在 JS 层借助算法先对比出具体有哪些真实 DOM 需要被改变,然后再将这些改变作用于真实 DOM。

3. 没有虚拟DOM时期的解决方案

3.1 原生 JS 操作DOM

在前端这个工种的萌芽阶段,前端页面“展示”的属性远远强于其“交互”的属性。

3.2 jQuery 时期

原生 JS 提供的 DOM API,实在是太太太太太难用了。jQuery 使 DOM 操作变得简单、快速,并且始终确保其形式稳定、可用性稳定。

3.3 早期模板引擎方案

jQuery 帮助我们能够以更舒服的姿势操作 DOM,但它并不能从根本上解决 DOM 操作量过大情况下前端侧的压力。

模板引擎一般需要做下面几件事情:

  • 读取 HTML 模板并解析它,分离出其中的 JS 信息;
  • 将解析出的内容拼接成字符串,动态生成 JS 代码;
  • 运行动态生成的 JS 代码,吐出“目标 HTML”;
  • 将“目标 HTML”赋值给 innerHTML,触发渲染流水线,完成真实 DOM 的渲染。

使用模板引擎方案来渲染数据是非常爽的:每次数据发生变化时,我们都不用关心到底是哪里的数据变了,也不用手动去点对点完成 DOM 的修改。只需要关注的仅仅是数据和数据变化本身,DOM 层面的改变模板引擎会帮我们做掉。

在 DOM 操作频繁的场景下,模板引擎可能会直接导致页面卡死。

4. 虚拟 DOM 是如何解决问题的

image.png

从图中可以看出,DOM操作多出了一层虚拟 DOM 作为缓冲层。这个缓冲层带来的利好是:当 DOM 操作(渲染更新)比较频繁时,它会先将前后两次的虚拟 DOM 树进行对比,定位出具体需要更新的部分,生成一个“补丁集”,最后只把“补丁”打在需要更新的那部分真实 DOM 上,实现精准的“差量更新”。这个过程对应的虚拟 DOM 工作流如下图所示:

image.png

5. React 选用虚拟 DOM,真的是为了更好的性能吗?

虚拟 DOM 并不一定会带来更好的性能,它的优越之处在于,它能够在提供更爽、更高效的研发模式(也就是函数式的 UI 编程方式)的同时,仍然保持一个还不错的性能。

模板渲染和虚拟 DOM 在性能开销上的差异?

image.png

模板渲染的步骤1,和虚拟 DOM 渲染的步骤1、2都属于 JS 范畴的行为,动态生成 HTML 字符串的过程本质是对字符串的拼接,对性能的消耗是有限的;而虚拟 DOM 的构建和 diff 过程逻辑则相对复杂,它不可避免地涉及递归、遍历等耗时操作。因此在 JS 行为这个层面,模板渲染胜出。

模板渲染的步骤3,和虚拟 DOM 的步骤3 都属于 DOM 范畴的行为,模板渲染是全量更新,而虚拟 DOM 是差量更新。

乍一看好像差量更新一定比全量更新高效,但你需要考虑这样一种情况:数据内容变化非常大(或者说整个发生了改变),促使差量更新计算出来的结果和全量更新极为接近(或者说完全一样)。此时 DOM 更新的工作量基本一致,而虚拟 DOM 却伴随着开销更大的 JS 计算。那么虚拟 DOM 大概率不敌模板渲染。但只要两者在最终 DOM 操作量上拉开那么一点点的差距,虚拟 DOM 就将具备战胜模板渲染的底气。因为虚拟 DOM 的劣势主要在于 JS 计算的耗时,而 DOM 操作的能耗和 JS 计算的能耗根本不在一个量级,极少量的 DOM 操作耗费的性能足以支撑大量的 JS 计算。

在实际的开发中,更加高频的场景是修改少量的数据。在这种场景下虚拟 DOM 将在性能上具备绝对的优势。

所以虚拟 DOM 的价值不在性能,而在别处。
**

6.虚拟 DOM 的价值到底是什么

虚拟DOM解决的关键问题:

  • 研发体验/研发效率的问题:虚拟 DOM 的出现,为数据驱动视图这一思想提供了高度可用的载体,使得前端开发能够基于函数式 UI 的编程方式实现高效的声明式编程。
  • 跨平台的问题:虚拟 DOM 是对真实渲染内容的一层抽象。若没有这一层抽象,那么视图层将和渲染平台紧密耦合在一起。

除了差量更新以外,“批量更新”也是虚拟 DOM 在性能方面所做的一个重要努力,“批量更新”是由 batch 函数来处理的。在差量更新速度非常快的情况下(比如极短的时间里多次操作同一个 DOM),用户实际上只能看到最后一次更新的效果。这种场景下,前面几次的更新动作虽然意义不大,但都会触发重渲染流程,带来大量不必要的高耗能操作。

这时就需要请 batch 来帮忙了,batch 的作用是缓冲每次生成的补丁集,它会把收集到的多个补丁集暂存到队列中,再将最终的结果交给渲染函数,最终实现集中化的 DOM 批量更新。