怎么理解虚拟 DOM?

一、前言

现在web前端的开发,对于MVVM框架的运用,那是信手拈来,用的飞起。一个xxx-cli工具,就能初始化一套模板,再填充业务代码,打包部署即可。但是会用,是一个方面,大家有没有底层深入思考一下,这些框架核心的技术突破点在哪里?解决了哪些问题?作为一个爱主动学习的童鞋,我们得花点小时间稍微去研究一下才行。今天,我们就简单谈谈虚拟dom,来揭开它的神秘面纱。

首先看一道经典的面试题:

“为什么我们需要虚拟 DOM?”。

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

但实际真实这样吗?

二、虚拟dom是什么?what。

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

  • 1.虚拟dom是一个js对象
  • 2.虚拟dom能够描述真实dom(存在一个对应关系)
  • 3.存储在内存之中

以react为例,我们来看看虚拟dom到底长什么样子: 

三、为什么会有虚拟dom?why。

首先,我们来看看远古时代(可能就是在7,8年前),我们怎么操作dom的。

1、什么都不用,原生js

来看看原生js下我们怎么查找元素的

// 根据类名查询元素,返回一个满足查询条件数组
document.getElementsByClassName()

// 根据标签名查询元素,返回一个满足查询条件数组
document.getElementsByTagName()

// 根据标签名查询元素,返回一个满足查询条件结果
document.getElementById()

.....

来看看我们怎么动态修改dom的

var displayName = 'hello,admin';
document.getElementById('#id').innerHtml = '<div>' + displayName+'</div>';

window.document.body.append('<div>hahahha</div>');

大家觉得这么操作dom会高效吗?

2、jquery时代

jQuery在操作dom时,对DOM API 封装为了相对简单和优雅的形式,同时一口气做掉了跨浏览器的兼容工作,并且提供了链式 API 调用、插件扩展等一系列能力用于进一步解放生产力。最终达到的效果正是我们喜闻乐见的“写得更少,做得更多”。

jQuery 使 DOM 操作变得简单、快速,并且始终确保其形式稳定、可用性稳定。虽然现在看来并不完美,但是当年能够一统江湖,确实在解放生产力,提高效率方面有极大的促进作用。

$('#id').show();
$('.className').hide().html('xxxxx');
3、模板引擎

我们以Handlebars这个模板为例

<!-- Include Handlebars from a CDN -->
<script src="//cdn.jsdelivr.net/npm/handlebars@latest/dist/handlebars.js"></script>
<script>
  // compile the template
  var template = Handlebars.compile("Handlebars <b>{{doesWhat}}</b>");
  // execute the compiled template and print the output to the console
  console.log(template({ doesWhat: "rocks!" }));
 

</script>
{
  people: [
    "Yehuda Katz",
    "Alan Johnson",
    "Charles Jolley",
  ],
}
 <ul class="people_list">
  {{#each people}}
    <li>{{this}}</li>
  {{/each}}
</ul>

用模板引擎,来操作js,效率还是挺高的,爽歪歪啊。 每次数据发生变化时,我们都不用关心到底是哪里的数据变了,也不用手动去点对点完成 DOM 的修改。只需要关注的仅仅是数据和数据变化本身,DOM 层面的改变模板引擎会帮我们做掉。

类似这种模板引擎,还有很多,我们可以对比一下其性能是怎样的: 

模板引擎像极了一个只需要接收命令,就能够把活干得漂漂亮亮的“扫地机器人”!可惜的是,模板引擎出现的契机虽然是为了使用户界面与业务数据相分离,但实际的应用场景基本局限在实现高效的字符串拼接这一个点上,因此不能指望它去做太复杂的事情。尤其令人无法接受的是,它在性能上的表现并不尽如人意:由于不够“智能”,它更新 DOM 的方式是将已经渲染出 DOM 整体注销后再整体重渲染,并且不存在更新缓冲这一说。在 DOM 操作频繁的场景下,模板引擎可能会直接导致页面卡死。

也就是说,其实我们想要高效迅速的操作dom,同时还保证一个不错的性能。修改了局部dom就替换局部。而不是整体全部替换。但是如果一直都是操作真实的dom,一直都是存在DOM 整体注销后再整体重渲染的情况,那性能效率方面就大打折扣了。—-react团队想了想,我们操作假的dom不就行了吗?

因此 虚拟dom就自然而然的应运而生。

三、虚拟dom怎本工作的?how。

借用大神的一张图来说明:

由此图我们可以得出:

数据 + 模板 => 虚拟dom

这里的模板,不一定是我们之前讲的Handlebars这样的js模板引擎。在react中,这种模板是以jsx的形势存在。jsx 是Javascript 的一种语法扩展。你可以理解为在中react使用体验和模板相似的 JS 语法糖。

相比于之前传统的修改dom的方式,这种修改就是多了一个缓冲区。当 DOM 操作(渲染更新)比较频繁时,它会先将前后两次的虚拟 DOM 树进行对比,定位出具体需要更新的部分,生成一个“补丁集”,最后只把“补丁”打在需要更新的那部分真实 DOM 上,实现精准的“差量更新”。 

需要注意的是,我们这里为了方便讲解虚拟dom,会借助react举例来说明,实际上,vue也支持虚拟dom。也就是说虚拟dom,是不依赖框架的。但是 react 主推虚拟dom,想更深入的掌握react,必须要了解虚拟dom。

在整个 DOM 操作的演化过程中,主要矛盾并不在于性能,在于研发体验/研发效率(在于开发者写得爽不爽)。虚拟 DOM 不是别的,正是前端开发们为了追求更好的研发体验和研发效率而创造出来的高阶产物。

虚拟 DOM 并不一定会带来更好的性能,React 官方也从来没有把虚拟 DOM 作为性能层面的卖点对外输出过。虚拟 DOM 的优越之处在于,它能够在提供更爽、更高效的研发模式(也就是函数式的 UI 编程方式)的同时,仍然保持一个还不错的性能。

四、性能浅析对比

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

  • 模板和虚拟 DOM 在最后更新dom阶段,都属于 DOM 范畴的行为,两者具备可比性,因此我们仍然可以愉快地对比下去:模板渲染是全量更新,而虚拟 DOM 是差量更新。

一般来说,更新一定比全量更新高效,但你需要考虑这样一种情况:数据内容变化非常大(或者说整个发生了改变),促使差量更新计算出来的结果和全量更新极为接近(或者说完全一样)。那么虚拟dom在更新dom阶段没有性能优势可言。

在这种情况下,DOM 更新的工作量基本一致,而虚拟 DOM 却伴随着开销更大的 JS 计算,此时会出现的一种现象就是模板渲染和虚拟 DOM 在整体性能上难分伯仲:若两者最终计算出的 DOM 更新内容完全一致,那么虚拟 DOM 大概率不敌模板渲染;但只要两者在最终 DOM 操作量上拉开那么一点点的差距,虚拟 DOM 就将具备战胜模板渲染的底气。因为虚拟 DOM 的劣势主要在于 JS 计算的耗时,而 DOM 操作的能耗和 JS 计算的能耗根本不在一个量级,极少量的 DOM 操作耗费的性能足以支撑大量的 JS 计算。

在实际的开发中,更加高频的场景是这样的:我每次 setState 的时候只修改少量的数据,比如一个对象中的某几个属性,再比如一个数组中的某几个元素。在这样的场景下,模板渲染和虚拟 DOM 之间 DOM 操作量级的差距就完全拉开了,虚拟 DOM 将在性能上具备绝对的优势。

同时,我们考虑这种情况:短时间内对3种状态进行修改,有虚拟dom这个缓冲,react可以将其加入到批量更新的任务中去。而不是每次不停的立即更新真是dom。

五、虚拟dom的意义

回到最开始的问题:为什么我们需要虚拟dom?

在整个 DOM 操作的演化过程中,主要矛盾并不在于性能,而在于开发者写得爽不爽,在于研发体验/研发效率。虚拟 DOM 不是别的,正是前端开发们为了追求更好的研发体验和研发效率而创造出来的高阶产物。虚拟 DOM 并不一定会带来更好的性能,React 官方也从来没有把虚拟 DOM 作为性能层面的卖点对外输出过。虚拟 DOM 的优越之处在于,它能够在提供更爽、更高效的研发模式(也就是函数式的 UI 编程方式)的同时,仍然保持一个还不错的性能。

虚拟 DOM 解决的关键问题有以下两个。

  • 1、研发体验/研发效率的问题:这一点前面已经反复强调过,DOM 操作模式的每一次革新,背后都是前端对效率和体验的进一步追求。虚拟 DOM 的出现,为数据驱动视图这一思想提供了高度可用的载体,使得前端开发能够基于函数式 UI 的编程方式实现高效的声明式编程。

  • 2、跨平台的问题:虚拟 DOM 是对真实渲染内容的一层抽象。若没有这一层抽象,那么视图层将和渲染平台紧密耦合在一起,为了描述同样的视图内容,你可能要分别在 Web 端和 Native 端写完全不同的两套甚至多套代码。但现在中间多了一层描述性的虚拟 DOM,它描述的东西可以是真实 DOM,也可以是iOS 界面、安卓界面、小程序……同一套虚拟 DOM,可以对接不同平台的渲染逻辑,从而实现“一次编码,多端运行”,

归根结底,解决跨平台的问题,也是提高研发效率。