虚拟DOM与diff算法

虚拟DOM与diff算法

我们的学习是个不断循序渐进的过程,所以在这学习应用过程之中,我们需要了解一些关于底层与原理的内容,因为自己接触vue比较多,所以这次写点关于虚拟DOM与diff算法,谈些个人理解,希望能帮助理解。
要想明白虚拟dom,我们首先要知道浏览器页面的渲染过程,我们都知道浏览器不能直接解析类似于vue、sass等文件,所以VUE项目最终运行需要我们将其打包成浏览器所能识别的html、js、css文件,下面就是浏览器页面渲染此的一般基本过程:
1、解析html源码,构建DOM树;
2、解析CSS代码,构建CSSOM树;
3、将DOM Tree和CSSOM结合,构建rendering tree(渲染树);
4、根据渲染树直接绘制页面,展示到屏幕上;
整个过程用下图来表示比较容易理解:

浏览器渲染页面过程
注:以上图只是简略过程便于理解用,详细内容如src和link的阻塞作用,具体解析过程不做过多介绍,有兴趣的人可以参阅相关文档。

从上面我们不难看出,当我们每次操作dom时候,都会使得DOM树产生变化,从而需要重新解析构建DOM树,由此我们也需要重新构建Rendering树(渲染树),当存在频繁的DOM操作时,会浪费大量的性能。
我们在来看由VUE项目渲染页面的过程:
1、Vue通过编译将template 模板转换成渲染函数(render ) ,执行渲染函数就可以得到一个虚拟节点树
2、在对 Model 进行操作的时候,会触发对应 Dep 中的 Watcher 对象。Watcher 对象会调用对应的 update 来修改视图。这个过程主要是将新旧虚拟节点进行差异对比,然后根据对比结果进行DOM操作来更新视图。
借用一张别人的图,过程如下:
在这里插入图片描述
引入一些概念讲解:
渲染函数:渲染函数是用来生成Virtual DOM的。Vue推荐使用模板来构建我们的应用界面,在底层实现中Vue会将模板编译成渲染函数,当然我们也可以不写模板,直接写渲染函数,以获得更好的控制。
VNode 虚拟节点:它可以代表一个真实的 dom 节点。通过 createElement 方法能将 VNode 渲染成 dom 节点。简单地说,vnode可以理解成节点描述对象,它描述了应该怎样去创建真实的DOM节点。
patch(patching算法):虚拟DOM最核心的部分,它可以将vnode渲染成真实的DOM,这个过程是对比新旧虚拟节点之间有哪些不同,然后根据对比结果找出需要更新的的节点进行更新。Vue的Virtual DOM Patching算法是基于Snabbdom的实现,并在些基础上作了很多的调整和改进。
由此我们可以知道,Virtual DOM(虚拟DOM) 其实就是一棵以 JavaScript 对象( VNode 节点)作为基础的树,用对象属性来描述节点,实际上它只是一层对真实 DOM 的抽象。最终可以通过一系列操作使这棵树映射到真实环境上,最终目标是将虚拟节点渲染到视图上。但是如果直接使用新虚拟节点全部覆盖旧节点的话,会有很多不必要的DOM操作(没有改变的内容也覆盖重新渲染了),在这里就需要用到我们下面将介绍的diff 算法,用于计算出Virtual DOM中真正变化的部分,并只针对该部分进行原生DOM操作,而非重新渲染整个页面

Diff算法有三大策略:
1、Tree Diff
2、Component Diff
3、Element Diff
Tree Diff:顾名思义,是对树每一层进行遍历,找出不同;
Component Diff:Component Diff 是数据层面的差异比较;
Element Diff:Element Diff真实DOM渲染,结构差异的比较;
在这里插入图片描述

传统diff:传统diff算法通过循环递归对节点进行依次对比,两两对比所花费的时间复杂度为O(n^2),找到差异后还要计算最小转换方式,所以传统算法时间复杂度达到 O(n^3) ,n是算法中问题的规模,这里即为树的节点数。对此,react和vue针对三大策略都进行了优化,将O(n^3)复杂度 转化为 O(n)复杂度。
React
对于策略一,React 对树进行了分层比较,两棵树只会对同一层次的节点进行比较。只会对相同层级的 DOM 节点进行比较,当发现节点已经不存在时,则该节点及其子节点会被完全删除,不会用于进一步的比较。如果出现了 DOM 节点跨层级的移动操作,就会被直接销毁;

对于策略二,React对不同的组件间的比较,分为三种清空
(1)同一类型的两个组件,按原策略(层级比较)继续比较Virtual DOM树即可。
(2)同一类型的两个组件,组件A变化为组件B时,可能Virtual DOM没有任何变化,如果知道这点(变换的过程中,Virtual DOM没有改变),可节省大量计算时间,所以 用户 可以通过 shouldComponentUpdate() 来判断是否需要 判断计算。
(3)不同类型的组件,将一个(将被改变的)组件判断为dirty component(脏组件),从而替换 整个组件的所有节点。

对于策略三,当节点处于同一层级时,diff提供三种节点操作:删除、插入、移动。
插入:内容不在于集合中,进行插入操作;
删除
(1)内容之前在集合中,后来不在了,需要删除;
(2)内容在集合中但是节点已经发生了变化,不能更新复用时,需要删除;
移动:内容在集合中,未发生改变或者可以进行更新复用,只需移动位置即可完成更新时,进行移动;
对比图引用网上查询到的资料图,是这样的:
在这里插入图片描述
而vue的diff的优化稍有不同,都是忽略跨级比较,只做同级比较。vue diff时调动patch函数,参数是vnode和oldVnode,分别代表新旧节点。

  1. vue比对节点,当节点元素类型相同,但是className不同,任务是不同类型元素,删除重建,而react会认为是同类型节点,只是修改节点属性
  2. vue的列表比对,采用从两端到中间的比对方式,而react则采用从左到右依次比对的方式。当一个集合,只是把最后一个节点移动到了第一个,react会把前面的节点依次移动,而vue只会把最后一个节点移动到第一个。总体上,vue的对比方式更高效。具体可以参考这篇文章

写在后面,因为写本文时主要是记录自己学习过程中一些经验理解,过程中查询了很多资料,对于特别底层的内容也没有进行深入剖析,可能会存在一些理解误差或错误,还望谅解,只做参考理解即可。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值