为什使用虚拟Dom

什么是虚拟dom

当说起vue和react时候,大家都不免会提到一个概念,就是Virtual DOM(虚拟Dom)。那么,这个虚拟Dom到底是个什么东西,为什么这两个伟大的框架都要使用呢。

首先Virtual DOM是一个映射真实DOM的JavaScript对象,如果需要改变任何元素的状态,那么是先在Virtual DOM上进行改变,而不是直接改变真实的DOM。当有变化产生时,一个新的Virtual DOM对象会被创建并计算新旧Virtual DOM之间的差别。之后这些差别会应用在真实的DOM上。

首先我们举个例子:

<ul class="list">
  <li>item 1</li>
  <li>item 2</li>
</ul>

对于上面的这个Dom树,我们可以简单的用javascript进行描述。

{
    type: 'ul', 
    props: {'class': 'list'}, 
    children: [
        { type: 'li', props: {}, children: ['item 1'] },
        { type: 'li', props: {}, children: ['item 2'] }
    ]
}

当然,上面的这个Dom树是比较简单的Dom,真实的Dom树远比这个要复杂的多。但是其本质上都是一样的,就是一个嵌套着数组的原生对象。

当新一项被加进去这个JavaScript对象时,一个函数会计算新旧Virtual DOM之间的差异并反应在真实的DOM上。计算差异的算法是高性能框架的秘密所在,React和Vue在实现上有点不同。

Vue宣称可以更快地计算出Virtual DOM的差异,这是由于它在渲染过程中,会跟踪每一个组件的依赖关系,不需要重新渲染整个组件树。

而对于React而言,每当应用的状态被改变时,全部子组件都会重新渲染。当然,这可以通过shouldComponentUpdate这个生命周期方法来进行控制,但Vue将此视为默认的优化。

为什么要用虚拟Dom

这个问题,其实就是为了对比虚拟Dom和真实Dom的区别,以及使用虚拟Dom有什么优势。很多人都觉得虚拟Dom比真实Dom快。但是真是如此么?

首先我们举个例子:向页面中插入一个元素,不管是按钮还是别的,我们会怎么去做:

document.getElementById('box').innerHTML = '<button>按钮</button>'

但是如果我们向一个盒子中添加10000个button呢,这个时候我们会怎么去做;

var _d1 = new Date();
for(var index = 0; index < 10000; index ++) {
  var button = document.createElement('button');
  button.innerText = '按钮'
  document.getElementById('box').append(button);
}
var _d2 = new Date();
console.log(_d2 - _d1)

我们可以一个一个去网页里添加,但是当我们执行这段代码的时候,我们会发现,添加的耗时已经,打印出来了,但是浏览器,还要一会儿才会输出内容。其实这个过程是在进行重排重绘,对于有些浏览器来说,会在我们添加完每一个具有占位的dom的时候,都会进行重排和重绘,我么知道,重排和重绘其实代价是非常的昂贵的。因此我们需要对上述的代码进行优化,优化的结果如下:

var _d1 = new Date();
var _dom = ''
for(var index = 0; index < 10000; index ++) {
  _dom += '<button>按钮</button>'
}
document.getElementById('box').innerHTML = _dom;
var _d2 = new Date();
console.log(_d2 - _d1)

我么对比一下上面的两组结果第一个是40ms,第二个是16ms,经过这么对比,我们明显能够发现,其实后者的效率要高于前者。

如果我们创建了这10000个按钮,我们希望对其中的某些进行修改,我们可以做的就是找到每一个按钮,然后进行逐个修改,并且进行逐个重排重绘,这样做会造成多次的重排和重绘;还有一种方案就是我们将所需要进行修改的按钮,进行重新生成,组织成dom,然后通过innerHTML插入到页面中,进行重排和重绘,这样虽说能够减少重排和重绘的次数,但是我们创建元素的个个数会变得很多,同样也是一件很浪费性能的事儿。

接下来就该我们的虚拟Dom出场了,如果基于虚拟Dom去完成如上的内容,创建10000个按钮其本质上是和方法二很类似。我们将10000个按钮创建好,然后生成对应的html,再一次性的设置进去。但对于更新来说,就有所不同, 其更新是先修改虚拟dom,然后进行Diff,然后将必要的Dom元素更新到页面上。

  • nnerHTML: render html string O(template size) + 重新创建所有 DOM 元素 O(DOM size)
  • Virtual DOM: render Virtual DOM + diff O(template size) + 必要的 DOM 更新 O(DOM change)

Virtual DOM render + diff 显然比渲染 html 字符串要慢,但是!它依然是纯 js 层面的计算,比起后面的 DOM 操作来说,依然便宜了太多。可以看到,innerHTML 的总计算量不管是 js 计算还是 DOM 操作都是和整个界面的大小相关,但 Virtual DOM 的计算量里面,只有 js 计算和界面大小相关,DOM 操作是和数据的变动量相关的。前面说了,和 DOM 操作比起来,js 计算是极其便宜的。这才是为什么要有 Virtual DOM:它保证了

  1. 不管你的数据变化多少,每次重绘的性能都可以接受
  2. 你依然可以用类似 innerHTML 的思路去写你的应用。

但是针对一个特别大的项目,而且交互复杂的项目来说,我们面临着两个问题,一个是可维护性,一个是代码的执行性能。让你用更声明式的方式来描述你的目的,从而让你的代码更容易维护,但是没有纯手动的优化 DOM 操作快。因为我们的虚拟的 DOM 操作层需要应对任何上层 API 可能产生的操作,它的实现必须是普适的。针对于每一个组件我们都可以进行优化,并且构建一个应用。但是那样又有什么用,最后搞得整个项目一团糟,最后完全失去了可维护性。因此在超高性能和可维护性方面,我们选择了比较折中的办法就是使用虚拟dom,然后生成框架。

小结

如果你的应用中,交互复杂,需要处理大量的UI变化,那么使用Virtual DOM是一个好主意。如果你更新元素并不频繁,那么Virtual DOM并不一定适用,性能很可能还不如直接操控DOM。

猜你喜欢

转载自www.cnblogs.com/ShuiNian/p/12914085.html