虚拟DOM

摘自:深入浅出vue.js一书
1.什么是虚拟DOM

虚拟DOM是随时代发展而诞生的产物。在web早期,页面的交互效果比现在简单得多,没有复杂的状态需要管理,也不需要频繁地操作DOM,使用jQuery来开发就可以满足我们的需求。
但是随着时代的发展,页面上的功能越来越多,需要实现的需求也越来越复杂,程序中需要维护的状态也越来越多,DOM操作也越来越频繁,此时我们会发现如果像之前那样使用jQuery来开发页面,那么代码会有很多操作DOM,程序中的状态也很难管理,代码逻辑也会很混乱。这其实就是命令式操作DOM的问题,虽然简单易用,但业务复杂则不好维护。
而现在,我们使用的三大主流框架Vue.js,Angular和React都是声明式操作DOM.我们通过描述状态和DOM之间的映射关系,就可以将状态渲染成视图,关于状态到视图的转换过程,框架会帮我们做,不需要手动操作DOM.
说明:事实上,任何应用都有状态,并不是只有使用了现在流行框架之后才有的状态.只不过现代框架揭露了一个事实,就是我们关注点应该聚集在状态维护上,而DOM操作是可以省略掉的.使用jQuery开发的应用也是有状态的,其中所使用的变量都是状态.
状态可以是javascript的任意类型.Object,Array,String,Number,Boolean等都可以作为状态,这些状态最终会以段落,表单,链接或按钮等元素呈现在用户界面上,具体地说可以呈现在页面上.
本质上,我们作为状态作为输入,并生成DOM输出到页面上显示处理,这个过程叫做渲染.
在这里插入图片描述
然而通常程序在运行时,状态会不断发送变化(引起状态变化的原因有很多,有可能是用户点击了某个按钮,也有可能是某个ajax请求,这些行为都是异步发生的,理论上,所有异步行为都有可能引起状态变化).每当状态发生变化时,都需要重新渲染.那么如何确定状态中发生了什么变化以及需要在哪里更新DOM?在这种情况下,最简单粗暴的方式是既不需要关心在哪里更新DOM,我们只需要把所有DOM全删了,然后使用状态重新生成一份DOM,并将其输出到页面上显示出来就好了.
但是,访问DOM是非常昂贵的,按照上面的方式做,会造成相当多的性能浪费,状态变化通常只有有限个节点需要重新渲染,所以我们不仅需要找出哪里需要更新,还需要尽可能少地访问DOM.
解决:如图5-2所示:当某个状态发生变化时,只更新与这个状态相关联的DOM节点
这个问题有很多种解决方案.目前各大主流框架都有自己的一套解决方案,在Angular中就是脏检查的流程,React中使用虚拟DOM,vue.js 1.0通过细粒度的绑定.因此,虚拟DOM本质上是众多解决方案中的一种,可以用但并不一定必须用.
虚拟DOM的解决方式是通过状态生成一个虚拟节点树,然后使用虚拟节点树进行渲染。在渲染之前,会使用新生成的虚拟节点树和上一次生成的虚拟节点树进行对比,只渲染不同的部分。
在这里插入图片描述
虚拟节点树其实是由组件树建立起来的整个虚拟节点( Virtual Node,也经常简写为vnode)树。
图5-3给出了一颗虚拟节点树的样子
在这里插入图片描述

2.为什么要引入虚拟DOM

事实上,Angular 和React的变化侦测有一个共同点,那就是它们都不知道哪些状态( state)变了。因此,就需要进行比较暴力的比对,React 是通过虚拟DOM的比对, Angular 是使用脏检查的流程。
Vue,js的变化侦测和它们都不一-样, 它在一定程度上知道具体哪些状态发生了变化,这样就可以通过更细粒度的绑定来更新视图。也就是说,在Vue,js中,当状态发生变化时,它在一定程度上知道哪些节点使用了这个状态,从而对这些节点进行更新操作,根本不需要比对。事实上,在Vue,js 1.0 的时候就是这样实现的。
但是这样做其实也有一定的代价。因为粒度太细,每一个绑定都会有一个对应的watcher来观察状态的变化,这样就会有一些内存开销以及一些依赖追踪的开销。当状态被越多的节点使用时,开销就越大。对于一个大型项目来说,这个开销是非常大的。
因此,Vue.js 2.0开始选择了一个中等粒度的解决方案,那就是引人了虚拟DOM。组件级别是一个watcher实例,就是说即便一个组件内有 10个节点使用了某个状态,但其实也只有一个watcher在观察这个状态的变化。所以当这个状态发生变化时,只能通知到组件。然后组件内部通过虚拟DOM去进行比对与渲染。这是一个比较折中的方案。
Vue,js之所以能随意调整绑定的粒度,本质上还要归功于变化侦测。

3.vue.js中的虚拟DOM

在Vue,js中,我们使用模板来描述状态与DOM之间的映射关系。Vuejs 通过编译将模板转换成渲染函数( render ),执行渲染函数就可以得到一个虚拟节点树,使用这个虚拟节点树就可以渲染页面,具体如图5-4所示。
在这里插入图片描述
虚拟DOM的终极目标是将虚拟节点( vnode)渲染到视图上。但是如果直接使用虚拟节点覆盖旧节点的话,会有很多不必要的DOM操作。
例如,一个ul标签下有很多li标签,其中只有一个li有变化,这种情况下如果使用新的ul去替换旧的ul,其实除了那个发生了变化的li节点之外,其他节点都不需要重新渲染。由于DOM操作比较慢,所以这些DOM操作在性能上会有一定的浪费, 避免这些不必要的DOM操作会提升很大一部分性能。为了避免不必要的DOM操作,虚拟DOM在虚拟节点映射到视图的过程中,将虚拟节点与上一次渲染视图所使用的旧虚拟节点( oldVnode )做对比,找出真正需要更新的节点来进行DOM操作,从而避免操作其他无任何改动的DOM。
图5-5给出了虚拟DOM的整体运行流程,先将vnode与oldVnode做比对,然后再更新视图。
在这里插入图片描述
可以看出,虚拟DOM在Vue,js中所做的事情其实并没有想象中那么复杂,它主要做了两件事。
①提供与真实DOM节点所对应的虚拟节点vnode。
②将虚拟节点vnode和旧虚拟节点oldVnode进行比对,然后更新视图。
vnode是JavaScript中-一个很普通的对象,这个对象的属性上保存了生成DOM节点所需要的一些数据,我们在之后会对vnode进行详细的介绍。对两个虚拟节点进行比对是虚拟DOM中最核心的算法(即patch),它可以判断出哪些节点发生了变化,从而只对发生了变化的节点进行更新操作。关于patch, 我们会在之后详细介绍。

4.总结

虚拟DOM是将状态映射成视图的众多解决方案中的一种, 它的运作原理是使用状态生成虚拟节点,然后使用虚拟节点渲染视图。
之所以需要先使用状态生成虚拟节点是因为如果直接用状态生成真实DOM,会有一定程度的性能浪费。而先创建虚拟节点再渲染视图,就可以将虚拟节占缓存,然后使用新创建的虚拟节点和上一次渲染时缓存的虚拟节点进行对比,然后根据对比结果只更新需要更新的直实DOM节点。从而避免不必要的DOM操作,节省一定 的性能开销。
由于Vue.js的变化侦测粒度更细,所以当状态发生变化时,Vue.js 知道的信息更多,一定程度上可以知道哪些位置使用了状态。因此,Vuejs 可以通过细粒度的绑定来更新视图,Vue,js 1.0就是这样实现的。但是这样做也有一定的代价。 因为粒度太细,就会有很多watcher同时观察某些状态,会有一些内存开销以及一些依赖追踪的开销,所以Vue,js 2.0采取了一个中等粒度的解决方案,状态侦测不再细化到某个具体节点,而是某个组件,组件内部通过虚拟DOM来渲染视图,这可以大大缩减依赖数量和watcher数量。
Vue.js中通过模板来描述状态与视图之间的映射关系,所以它会先将模板编译成渲染函数,然后执行谊染函数生成虚拟节点,最后使用虚拟节点更新视图。
因此,虚拟DOM在Vue.js中所做的事是提供虚拟节点vnode和对新旧两个vnode进行比对,并根据比对结果进行DOM操作来更新视图。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值