一、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
就是将多次比较的结果合并后一次性更新到页面,从而有效地减少页面渲染的次数,提高渲染效率。
四、Vue
中的Virtual DOM
?
在Vue.js
中,我们使用模板来描述状态与DOM
之间的映射关系。Vue.js
通过编译将模板转换成渲染函数(render
),执行渲染函数就可以得到一个虚拟节点树,使用这个虚拟节点树就可以渲染页面。
虚拟DOM
的终极目标时将虚拟节点(vnode
)渲染到视图上。但是如果直接使用虚拟节点覆盖旧节点的话,会有很多不必要的DOM操作。
由于操作DOM
比较慢,所以这些DOM
操作在性能上会有一定的浪费,避免这些不必要的DOM
操作会提升很大一部分的性能。
喂了避免不必要的DOM
操作,虚拟DOM
在虚拟节点映射到视图的过程中,将虚拟节点与上一次渲染视图所使用的旧虚拟节点(oldVnode
)做对比,找出真正需要更新的节点进行DOM
操作,从而避免操作其他无任何改动的DOM
。
可以看出,虚拟DOM
在Vue.js
中所做的事情其实并没有想象中那么复杂,它主要做了两件事:
- 提供与真实
DOM
节点所对应的虚拟节点vnode
- 将虚拟接点
vnode
和旧虚拟节点oldVnode
进行对比,然后更新视图 对两个虚拟节点进行比对时许你DOM
中最核心的算法(即patch
),它可以判断出那些话节点发生了变化,从而只对发生了变化的节点进行更新操作。
五、区别
根据上面的简单介绍,其实Virtual DOM
就是一个JS
对象,最终这个JS
对象可以转换成真实的DOM
解构,那Vue
、React
的Virtual 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
这个生命周期函数方法来进行控制。