从性能方面探讨-为什么需要虚拟DOM

113 阅读5分钟

关于这个问题,工作中我曾经问过比我年限稍浅的同事,得到的答案,比较简单粗暴:为什么需要虚拟DOM?就是为了提高性能啊。言下之意,使用虚拟DOM更新真实DOM性能就是比直接JavaScript操作DOM的性能高。

什么是虚拟 DOM?

虚拟DOM简单一点说就是一个JavaScript对象包含 tagpropschildren 三个属性。 先抛出一个问题:通过操作虚拟DOM,然后映射到真实DOM,性能比直接操作真实DOM的性能好吗? 对于这个问题,我也参考了不少其他作者的文章,也包括《vue.js设计与实现》 我觉得这里要分两种情况,先说结论:

1.简单的操作少量DOM的时候直接操作DOM的性能更好

2.大量复杂的操作DOM的时候通过虚拟DOM往往性能更好

虚拟DOM的性能究竟如何?

以下文字摘自《vue.js设计与实现》

假设现在我们要将div的文本标签的文本内容修改成hello vue3,那么如何用命令式代码实现能?

div.textContent='hello vue3'

现在思考一下,有没有其他办法比上面这句代码的性能更好?答案是“没有”。可以看到,理论上命令式代码可以做到极致的性能优化,因为我们明确知道哪些发生了变更,只做必要的修改就行了。但是声明式代码做不到这一点,因为它描述的是结果:

<!--之前-->

<div @click="()=>alert('ok')">hello world</div>

<!--之后-->

<div @click="()=>alert('ok')">hello vue3</div>

对于框架来说,为了实现最优的更新性能,它需要找到前后的差异并只更新变化的地方,但是最终完成这次更新的代码仍然是:

div.textContent='hello vue3'

如果我们把直接修改的性能消耗定义为A,把找出差异的性能消耗定义为B,那么有:

命令式代码的更新性能消耗=A

声明式代码的更新性能消耗=B+A

可以看到,声明式代码会比命令式代码多出找出差异的性能消耗,因此最理想的情况是,当找出消耗的性能消耗为0时,声明式代码与命令式代码的性能相同,但是无法做到超越。也就是说在理想情况下声明式代码的性能不优于命令式代码的性能。

声明式代码的更新性能损耗 = 找出差异的性能损耗 + 直接修改的性能损耗

因此如果我们能够最小化找出差异的性能损耗,就可以让声明式代码的性能无限接近于命令式代码的性能了。而所谓的虚拟DOM,就是为了最小化找出差异这一步的性能损耗而出现的。

至此,你应该清楚一件事,那就是采用虚拟DOM的更新技术的性能理论上不可能比原生JavaScript操作DOM更高。这里我们强调理论上三个字,这很关键,因为在大多数情况下,我们很难写出绝对优化的命令式代码, 尤其是当应用程序的规模很大的时候,即使你写出了极致优化的代码,也一定耗费了巨大的精力,这时的投入产出比其实并不高。

那么,有没有什么办法能够让我们不用付出太多努力(写声明式代码),还能够保证应用程序的性能下限,让应用程序的性能不至于太差,甚至想办法逼近命令式代码的性能呢?这其实就是虚拟DOM要解决的问题。

虚拟DOM通过diff算法对比更新前后的虚拟DOM,得出真实DOM需要更新的最小元素子集,从而使真实DOM更新的时候,元素最大程度上得到复用,从而达到最小化找出差异的性能损耗的目的。

什么时候使用虚拟DOM的性能更优?

前面说了,当应用程序的规模很大的时候,我们很难写出极致优化的代码。想象一下,假如有如下情况:

<HTML>

1000行代码

</HTML>

这1000行代码里面需要渲染的是大气中某几项因子最近24小时的数据对应的图形,而这些数据是由仪器实时采集回来的。这些数据随着时间的推移,绘制出来的图形也不相同,也就是实时变化的,此时你还能写出精确修改DOM的JavaScript代码吗?此时,如果需要你用原生JavaScript进行操作,估计你就会简单粗暴的把原本的DOM移除掉,直接创建新的DOM然后替换上去吧?这么做,原来的DOM结构是不是完全就无法得到复用了呢?而此时如果是从服务器把数据读取出来,然后直接绑定到一个JavaScript对象上,至于应该如何更新DOM,那就交给虚拟DOM搞定,虚拟DOM会通过diff算法来保证尽可能多的复用原有DOM元素,这时候性能相对于直接替换,是不是就大大提升了呢?