为什么我们需要虚拟 DOM?

常规的回答是:
“DOM 操作是很慢的,而 JS 却可以很快,直接操作 DOM 可能会导致频繁的回流与重绘,JS 不存在这些问题。因此虚拟 DOM 比原生 DOM 更快”

但真的是这样吗?

虚拟 DOM 是什么

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

React 的虚拟 DOM 是如何工作

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

    历史长河中 DOM 操作解决方案

    原生 JS 支配下的 “人肉 DOM”时期

    前端页面“展示”的属性远远强于其“交互”的属性,这就导致 JS 的定位只能是“辅助”
    前端工程师们会花费大量的时间去实现静态的 DOM 待一切结束后,再补充少量 JS

    解放生产力的先导阶段:jQuery 时期

    大量 DOM 操作需求带来的前端开发工作量的激增
    jQuery 首先解决的就是“API 不好使”的问题 —— 将 DOM API 封装为相对简单和优雅的形式,同时一口气做掉了跨浏览器的兼容工作,并且提供了链式 API 调用、插件扩展等一系列能力用于进一步解放生产力。

    民智初启:早期模板引擎方案

    jQuery 并不能从根本上解决 DOM 操作量过大的情况下前端的压力
    好比一个手持吸尘器,虽然可以帮助我们更加方便快速地清洁某一处的灰尘,要想清洁多个位置的灰尘,仍然需要债券它四处奔走。还是避免不了跑断腿的结局。
  • 模板引擎方案,正是“扫地机器人”的雏形
  • 模板引擎更倾向于点对点解决烦琐 DOM 操作的问题,它在能力和定位上即不能够、也不打算替换掉 jQuery,两者是和谐共存,因此不存在“模板引擎时期”,只有“模板引擎方案”
  • 模板引擎的工作:
    1. 读取 HTML 模板并解析它,分享出其中的 JS 信息
    2. 将解析出的内容拼接成字符串,动态生成 JS 代码
    3. 运行动态生成的 JS 代码,吐出“目标 HTML”
    4. 将“目标 HTML”赋值给 innerHTML,触发渲染流水线,完成真实 DOM 的渲染
  • 使用模板引擎方案来渲染数据需要关注的仅仅是数据和数据变化本身
  • 局限在“实现高效的字符串拼接”这一个点上,因此不能指望做太复杂的事情,在性能上的表现不尽人意

image.png

走“数据驱动视图”

既然操作真实 DOM 能动性能损耗这么大,那操作假 DOM 不就行了?

真实历史中的虚拟 DOM 创作过程,到底有没能有向模板引擎去学习,这个暂时无从考证。但是按照前端发展过程来看,模板引擎和虚拟 DOM 确实在思想上存在递进的关系

image.png
这个模板是带上双引号,因为 React 是使用近似模板的 JSX
image.png

React 选择虚拟 DOM 真的是为了性能吗?

开发者写得爽不爽,在于 研发体验 / 研发效率

虚拟 DOM 是前端开发们为了追求更好的研发体验和研发效率,而创造出来的高阶产物

虚拟 DOM 的优越之处在于:能够在提供更爽、更高效的研发模式的同时,仍然保持一个还不错的性能

image.png :::info 数据内容变化非常大(或者说整个发生了改变),促使差量更新计算出来的结果和全量更新极为接近(或者说完全一样) ::: 虚拟 DOM 的劣势主要在于 JS 计算的耗时,DOM 操作的能耗和 JS 计算的能耗根本不在一个量级

:::info 每次 setState 的时候只修改少量的数据 ::: 模板渲染和虚拟 DOM 之间 DOM 操作量级的差距就完全拉开,虚拟 DOM 将在性能上具备绝对的优势

最终结论

虚拟 DOM 的价值不在性能,而在别处

  • 研发体验/研发效率的问题
  • 跨平台的问题
    image.png
    一次代码,多端运行
  • “批量更新”
    在通用虚拟 DOM 库里是由 batch 函数来处理,batch 的作用是缓冲每次生成的补丁集