Vue、React之Virtual DOM对比

730 阅读7分钟

一、Virtual DOM是什么?

Virtual DOM并不是凭空出现,而是随着时代发展而诞生的产物。通俗地说,Virtual DOM就是经过“伪装”的DOM,形似真实DOM,然而又无法替代真实的DOM

Virtual DOM是通过状态生成一个虚拟节点树,然后使用虚拟节点树进行渲染。在渲染之前,会使用新生成的虚拟节点树和上一次生成的虚拟节点树进行对比,只渲染不同的部分。

虚拟节点树其实是由组件树建立起来的整个虚拟节点(Virtual Node,也经常简写为vnode)树。

二、为什么需要Virtual DOM

随着时代的发展,页面的复杂度越来越高,程序中需要维护的状态也越来越多,DOM操作也越来越频繁,众所周知,操作DOM是非常损耗性能的。

DOM操作损耗性能

  • 线程切换 由于 JS 是可操纵 DOM 的,如果在修改这些元素属性同时渲染界面(即 JS 线程和渲染线程同时运行),那么渲染线程前后获得的元素数据就可能不一致了。因此为了防止渲染出现不可预期的结果,浏览器设置 渲染线程JS 引擎线程 为互斥的关系,当 JS 引擎执行时渲染线程会被挂起,GUI 更新则会被保存在一个队列中等到 JS 引擎线程空闲时立即被执行。

因此我们在操作 DOM 时,任何 DOM API 调用都要先将 JS 数据结构转为 DOM 数据结构,再挂起 JS 引擎线程并启动渲染引擎线程,执行过后再把可能的返回值反转数据结构,重启 JS 引擎继续执行。这种两个线程之间的上下文切换势必会很耗性能。

  • 重排、重绘

通过DOM的渲染过程我们可以看出,一次DOM渲染是具有很多个步骤,只要引起回流等操作,比如缩放页面,删除、增加页面元素,这些操作都会引起一次DOM渲染过程。进而得知,频繁操作DOM是一件非常昂贵的事情,消耗是非常大的。

如果上面的解释还是不能使你认为直接操作DOM非常的浪费,那接下来我们看一个比较直观的例子🌰

假设此时有一个非常复杂的场景,我需要更新10次DOM,而且每次都是紧接相连的,用户看到的只有第一次和第10次,同时过程可以认为包括很多的计算,但是经过10次之后,DOM又回到了最初的状态,在用户看来是没有任何变化的,然后我们可预料这10次的变化过程除经历了10遍DOM渲染之外,还包括10次复杂的就算,然后最终回到起点,相当于做了无用功,还损耗了性能。

那么重点来啦,Virtual DOM的作用终于要开始发挥啦!!!👏👏👏

Virtual DOM优势

“原生 DOM 操作太慢了,Virtual DOM 更快些”,其实没有任何框架可以比纯手动的优化DOM操作更快,因为框架的DOM操作层需要应对任何上层API可能产生的操作,它的实现必须是普遍适用的,因为开文第一句话也有说明 “面对越来越复杂的场景”,直接操作DOM会影响性能。

React 、Vue 从来没有说过 “比原生操作 DOM 快”。并不是说 Virtual DOM 操作一定是比原生 DOM 操作快,这和具体的页面模板大小和数据的变动量都有关系的 但是相比于操作 DOM,原生的 js 对象操作起来的确是会更快、更简单。

三、React中的Virtual DOM

引用 react 官网上的介绍:

Virtual DOM 是一种编程概念。在这个概念里, UI 以一种理想化的,或者说“虚拟的”表现形式被保存于内存中,并通过如 ReactDOM 等类库使之与“真实的” DOM 同步。这一过程叫做协调。

这种方式赋予了 React 声明式的 API:您告诉 React 希望让 UI 是什么状态,React 就确保 DOM 匹配该状态。这使您可以从属性操作、事件处理和手动 DOM 更新这些在构建应用程序时必要的操作中解放出来。

React为了减少不必要的DOM操作,Virtual DOM 在执行 DOM 的更新操作后,不会直接操作真实DOM,而是根据当前应用状态的数据,生成一个全新的 Virtual DOM,然后跟上一次生成 的 Virtual DOM diff,得到一个 patch,这样就可以找到变化了的 DOM 节点,只对变化的部分进行 DOM 更新,而不是重新渲染整个 DOM 树,这个过程就是 diff。还有所谓的patch就是将多次比较的结果合并后一次性更新到页面,从而有效地减少页面渲染的次数,提高渲染效率。

未命名文件 (8).png

四、Vue中的Virtual DOM

Vue.js中,我们使用模板来描述状态与DOM之间的映射关系。Vue.js通过编译将模板转换成渲染函数(render),执行渲染函数就可以得到一个虚拟节点树,使用这个虚拟节点树就可以渲染页面。

未命名文件 (6).png 虚拟DOM的终极目标时将虚拟节点(vnode)渲染到视图上。但是如果直接使用虚拟节点覆盖旧节点的话,会有很多不必要的DOM操作。 由于操作DOM比较慢,所以这些DOM操作在性能上会有一定的浪费,避免这些不必要的DOM操作会提升很大一部分的性能。 喂了避免不必要的DOM操作,虚拟DOM在虚拟节点映射到视图的过程中,将虚拟节点与上一次渲染视图所使用的旧虚拟节点(oldVnode)做对比,找出真正需要更新的节点进行DOM操作,从而避免操作其他无任何改动的DOM

未命名文件 (7).png 可以看出,虚拟DOMVue.js中所做的事情其实并没有想象中那么复杂,它主要做了两件事:

  • 提供与真实DOM节点所对应的虚拟节点vnode
  • 将虚拟接点vnode和旧虚拟节点oldVnode进行对比,然后更新视图 对两个虚拟节点进行比对时许你DOM中最核心的算法(即patch),它可以判断出那些话节点发生了变化,从而只对发生了变化的节点进行更新操作。

五、区别

根据上面的简单介绍,其实Virtual DOM就是一个JS对象,最终这个JS对象可以转换成真实的DOM解构,那VueReactVirtual DOM到底有什么不同呢,其实是Virtual DOM转换成真实DOM过程中的diff算法:

Vue中的diff算法:

  • 在内存中构建Virtual DOM

  • 将内存中Virtual DOM树渲染成真实DOM结构

  • 数据改变的时候,将之前的Virtual DOM树结合新的数据生成新的Virtual DOM

  • 将此次生成好的Virtual DOM树和上一次的Virtual DOM树进行一次比对(diff算法进行比对),来更新只需要被替换的DOM,而不是全部重绘。在Diff算法中,只平层的比较前后两棵DOM树的节点,没有进行深度的遍历。

  • 会将对比出来的差异进行重新渲染

React中的diff算法:

  • DOM结构发生改变-----直接卸载并重新create

  • DOM结构一样-----不会卸载,但是会update变化的内容

  • 所有同一层级的子节点.他们都可以通过key来区分-----同时遵循1.2两点(其实这个key的存在与否只会影响diff算法的复杂度,换言之,你不加key的情况下,diff算法就会以暴力的方式去根据一二的策略更新,但是你加了key,diff算法会引入一些另外的操作)

六、总结

React会逐个对节点进行更新,转换到目标节点。而最后插入新的节点,涉及到的DOM操作非常多。diff总共就是移动、删除、增加三个操作,而如果给每个节点唯一的标识(key),那么React优先采用移动的方式,能够找到正确的位置去插入新的节点。

Vue会跟踪每一个组件的依赖关系,不需要重新渲染整个组件树。而对于React而言,每当应用的状态被改变时,全部组件都会重新渲染,所以React中会需要shouldComponentUpdate这个生命周期函数方法来进行控制。