开篇
Vue.js2.0引入了虚拟DOM,比Vue.js1.0的初始渲染速度提升了2~4倍,并大大降低了内存消耗。
什么是虚拟DOM?
在Web早期,页面的交互效果简单的多,没有复杂的状态需要管理,使用JQuery来操作DOM完全满足需求,而现在随着时代的发展,页面上的功能越来越多,当DOM操作越来越频繁的时候,使用之前的方法来开发旧变得非常混乱,这其实就是命令式操作DOM存在的问题。
现在,前端三大主流框架Angular、Vue、React都是声明式操作DOM,我们只需要描述状态和DOM之间的映射关系,就可以将状态渲染成视图,状态到视图的过程,框架会帮我们完成,不需要手动去操作DOM结构。
通常在程序运行时,状态会不断发生变化,当发生变化时,会进行重新渲染,我们不仅需要知道哪里需要更新,还需要尽可能少地访问DOM。当状态发生变化时,只更新与它有关联的DOM节点。
Angular中使用的是脏检查地流程,而React中使用的是虚拟DOM,Vue.js1.0通过细粒度地绑定。
虚拟DOM的解决方式是通过状态生成一个虚拟节点树,然后使用虚拟节点树进行渲染。在渲染之前,会使用新生成的虚拟节点树和上一次生成的虚拟节点树进行对比,只渲染不同的部分。虚拟节点树其实是由组件树建立起来的整个虚拟节点(Virtual Node,简写VNode)树。
由于Vue.js采用更细粒度的绑定来更新视图,所以当状态发生变化时,在一定程度上知道哪些节点使用了这个状态,所以不需要暴力比对找到这个节点。但是由于粒度太细的原因,每一个绑定都会有一个对应的Watcher来观察状态的变化,这样如果状态多了以后,会有大量的内存开销以及一些依赖追踪的开销。因此Vue.js2.0使用了中等粒度的解决方法,引入虚拟DOM。组件级别是一个Watcher实例,Watcher只观察到组件,组件内部的状态发生变化时,只能通知到组件,然后组件内部采用虚拟DOM进行比对和渲染。
Vue.js中的虚拟DOM
Vue中,使用模板来描述状态与DOM之间的映射关系。通过编译将模板转换成渲染函数(render),执行渲染函数就可以生成一个虚拟节点树,使用这个虚拟节点树就可以渲染页面。
模板==编译==>渲染函数==执行==>VNode==patch==>视图
虚拟DOM的目标是将虚拟节点渲染到视图上。但是如果直接使用虚拟节点覆盖旧系欸但,会存在很多不必要的DOM操作,产生不必要的性能浪费。
在执行render渲染函数后,会生成VNode,会根据diff里面的patch来与上一次生成的old VNode进行比对,然后再更新视图
总结
虚拟DOM是将状态映射成视图的众多解决方案中的一种,它的运作原理是使用状态生成虚拟节点,然后使用虚拟节点渲染视图。
之所以需要先使用状态生成虚拟节点,是因为如果直接用状态生成真实DOM,会有一定程度的性能浪费。而先创建虚拟节点再渲染视图,就可以将虚拟节点缓存,然后使用新创建的虚拟节点和上一次渲染时缓存的虚拟节点进行对比,然后根据对比结果只更新需要更新的真实DOM节点,从而避免不必要的DOM操作,节省一定的性能开销。
由于Vue.js的变化侦测粒度更细,所以当状态发生变化时,Vue.js知道的信息更多,一定程度上可以知道哪些位置使用了状态。因此,Vue.js可以通过细粒度的绑定来更新视图,Vue.js1.0就是这样实现的。
但是这样做也有一定的代价。因为粒度太细,就会有很多watcher同时观察某些状态,会有一些内存开销以及一些依赖追踪的开销,所以Vue.js2.0采取了一个中等粒度的解决方案,状态侦测不再细化到某个具体节点,而是某个组件,组件内部通过虚拟DOM来渲染视图,这可以大大缩减依赖数量和Watcher数量,
Vue.js中通过模板来描述状态与视图之间的映射关系,所以它会先将模板编译成渲染函数,然后执行渲染函数生成虚拟节点,最后使用虚拟节点更新视图。
因此,虚拟DOM在Vue.js中所做的事是提供虚拟节点vnode和对新旧两个vnode进行比对,并根据比对结果进行DOM操作来更新视图。