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 是如何解决问题的
从图中可以看出,DOM操作多出了一层虚拟 DOM 作为缓冲层。这个缓冲层带来的利好是:当 DOM 操作(渲染更新)比较频繁时,它会先将前后两次的虚拟 DOM 树进行对比,定位出具体需要更新的部分,生成一个“补丁集”,最后只把“补丁”打在需要更新的那部分真实 DOM 上,实现精准的“差量更新”。这个过程对应的虚拟 DOM 工作流如下图所示:
5. React 选用虚拟 DOM,真的是为了更好的性能吗?
虚拟 DOM 并不一定会带来更好的性能,它的优越之处在于,它能够在提供更爽、更高效的研发模式(也就是函数式的 UI 编程方式)的同时,仍然保持一个还不错的性能。
模板渲染和虚拟 DOM 在性能开销上的差异?
模板渲染的步骤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 将在性能上具备绝对的优势。
6.虚拟 DOM 的价值到底是什么
虚拟DOM解决的关键问题:
- 研发体验/研发效率的问题:虚拟 DOM 的出现,为数据驱动视图这一思想提供了高度可用的载体,使得前端开发能够基于函数式 UI 的编程方式实现高效的声明式编程。
- 跨平台的问题:虚拟 DOM 是对真实渲染内容的一层抽象。若没有这一层抽象,那么视图层将和渲染平台紧密耦合在一起。
除了差量更新以外,“批量更新”也是虚拟 DOM 在性能方面所做的一个重要努力,“批量更新”是由 batch 函数来处理的。在差量更新速度非常快的情况下(比如极短的时间里多次操作同一个 DOM),用户实际上只能看到最后一次更新的效果。这种场景下,前面几次的更新动作虽然意义不大,但都会触发重渲染流程,带来大量不必要的高耗能操作。
这时就需要请 batch 来帮忙了,batch 的作用是缓冲每次生成的补丁集,它会把收集到的多个补丁集暂存到队列中,再将最终的结果交给渲染函数,最终实现集中化的 DOM 批量更新。