HeZulong

:

为什么使用虚拟 DOM

当我们被问到此题,很容易陷入“虚拟 DOM 为提升效率设计”的误区。如果以此结论作答,面对底层追问又很容易陷入被动。

那么虚拟 DOM 核心作用到底是什么,为什么需要它?

首先,我们需要认识到,虚拟 DOM 并非以“提升效率”为核心目的:

  1. 在 Vue、React 官方文档中,均无“虚拟 DOM 为提升效率设计”的相关表述;
  2. 框架 Svelte 未使用虚拟 DOM,但其运行效率优于多数使用虚拟 DOM 的前端框架;
  3. 无论是否使用虚拟 DOM,界面的最终更新都需要操作真实 DOM,若仅为提升效率,则无需额外增加虚拟 DOM 这一层。

所以将“提升效率”作为虚拟 DOM 的核心作用,该表述并不完善且存在明显的片面性。

那如何回答这样的问题呢,我们可以从 2 个方面下手。

框架设计

Vue、React 均为数据驱动框架,当数据发生变化时,仅更新页面中与该数据关联的 UI 部分。

理想状态下,数据变化应精准触发单个关联 DOM 元素的更新,该方式也无需虚拟 DOM 介入,且渲染效率最优。

但实际上,二者在框架设计的时候,可实现的最小更新单位(即颗粒度)就是组件,而非单个 DOM 元素。当组件内任意数据发生变化时,只能重新渲染整个组件,所以无法实现这一理想状态。

我们知道,在 Vue、React 中,都有一个渲染(render)函数,也就是数据一变,框架通过它触发渲染,也就是需要在这里去操作真实 DOM,仔细想想这会导致什么问题?

假设组件内包含大量真实 DOM 元素,仅因单个数据变化就对组件内所有真实 DOM 进行全量重新渲染,这必然会产生大量无意义的 DOM 操作,导致性能大幅损耗。

这是不可接受的!

所以框架选择了一种折中做法。

引入虚拟 DOM

所以当组件重新渲染时,框架通过渲染函数负责生成虚拟 DOM 树,再通过 Diff 算法对比新旧虚拟 DOM 树的差异,定位到仅需更新的 DOM 节点,最后对真实 DOM 进行批量、精准更新,从而避免全量渲染真实 DOM 带来的性能浪费。

解耦运行环境

上述提到的真实 DOM 是有一个前提,那就是运行在浏览器中。倘若需要运行在移动端 App、桌面应用等多种环境,这些非浏览器环境中没有真实 DOM,仅存在各自的原生组件,因此必须要解耦环境。

为了解耦,框架添加了一个中间层,即虚拟 DOM。

本质上,虚拟 DOM 就是一个普通 JS 对象,那么在所有支持 JS 运行的环境中均能被解析,它的核心作用就是为描述界面结构 — 仅定义界面包含的元素、属性及层级关系,不直接关联具体环境的渲染逻辑。

如果阅读 Vue、React 源码,你会发现二者均支持自定义渲染器,可将虚拟 DOM 的界面描述,转换为对应运行环境的渲染结果:在浏览器中渲染为真实 DOM;在移动端渲染为原生组件;在桌面应用中渲染为对应桌面组件。

基于该设计,框架实现了一套代码多端运行,这也是 React Native、UniApp 等多端框架的底层实现基础。

面试作答建议

针对这道题,我们可根据自身表达能力选择对应的作答思路,规避认知误区,体现对框架底层的理解。

如果表达能力不错,直接从框架设计解耦运行环境两个核心维度展开,结合上述逻辑说明虚拟 DOM 的价值。

如果表达能力欠缺,结合网上“八股文”简要说明,再通过转折引出核心观点 — 虚拟 DOM 的核心作用并非单一提升效率,其本质是框架在更新粒度限制下的折中选择,同时为框架的多端适配提供了底层支撑,简要解释两个核心维度的逻辑即可。

© HeZulong.