vue的diff算法原理

本文探讨了虚拟DOM中Diff算法的重要性,它用于高效地更新DOM,避免全量渲染。传统Diff算法复杂度高,React通过假设减少了复杂度到O(n)。Vue的Diff算法类似,采取同层比较、节点优先判断和就地复用策略,进一步提高效率。在Vue中,未找到匹配节点时会插入新节点,相同节点则更新属性,不同节点则直接替换。通过这种方式,Vue降低了DOM操作,提升了性能。
摘要由CSDN通过智能技术生成

1. 为什么要用Diff算法

由于在浏览器中操作DOM是很昂贵的,频繁的操作DOM,会产生一定的性能问题,这就是虚拟DOM的产生原因。虚拟DOM本质上是JavaScript对象,是对真实DOM的抽象状态变更时,记录新树与旧树的差异,最后把差异更新到真正的DOM中。

即使使用了Virtual DOM来进行真实DOM的渲染,在页面更新的时候,也不能全量地将整颗Virtual DOM进行渲染,而是去渲染改变的部分,这时候就需要一个计算Virtual DOM树改变部分的算法了,这个算法就是Diff算法。

diff算法的作用:用来修改DOM的一小段,不会引起dom树的重绘

2. 传统的Diff算法

传统的Diff算法通过循环递归对节点进行比较,然后判断每个节点的状态以及要做的操作(add,remove,change),最后 根据Virtual DOM进行DOM的渲染。
在这里插入图片描述
传统Diff算法的复杂度为O(n^3),这个复杂度相对来说还是较高的。后来React开发者提供了一种复杂度仅为O(n) 的Diff算法。

3. 更高效的Diff算法

React的开发者结合Web界面的特点做出了两个大胆的假设,使得Diff算法复杂度直接从O(n^3)降低到O(n),假设如下:

	两个相同组件产生类似的DOM结构,不同的组件产生不同的DOM结构;
	对于同一层次的一组子节点,它们可以通过唯一的id进行区分。

基于以上这两点假设,使得虚拟DOM的Diff算法的复杂度从O(n^3)降到了O(n)
在这里插入图片描述
同层比较
新的Diff算法是逐层进行比较,只比较同一层次的节点,不会跨层次比较,大大降低了复杂度
在这里插入图片描述
不同类型节点的比较
如果发现新旧两个节点类型不同时,Diff算法会直接删除旧的节点及其子节点并插入新的节点,这是由于前面提出的不同组件产生的DOM结构一般是不同的,所以可以不用浪费时间去比较。注意的是,删除节点意味着彻底销毁该节点,并不会将该节点去与后面的节点相比较。

相同类型节点的比较
若是两个节点类型相同时,Diff算法会重新设置该节点的属性,从而实现节点的更新。

列表节点的比较
列表节点的操作一般包括添加、删除和排序,列表节点需要我们给它一个key才能进行高效的比较。

4.Vue Diff算法的实现

了解了Diff算法的大体思路后,我们回过头来看下Vue中的Diff算法是如何实现的。

Vue的Diff算法与上面的思路大体相似,只比较同级的节点,若找不到与新节点类型相同的节点,则插入一个新节点,若有相同类型的节点则进行节点属性的更新,最后删除新节点列表中不包含的旧节点。具体的实现在vue源码的src/core/vdom/patch.js中的updateChildren方法中,由于代码较长,下面简单说一下整个的比较流程。

在这里插入图片描述
如上图,有一组新旧节点数组before:[A, B, C, D]、after:[E, C, F, G],我们设置了四个哨兵节点,oldStartIdx、newStartIdx、oldEndIdx、newEndIdx分别指向新旧节点数组的起始下标和开始下标,值为0,0,3,3;oldStartVnode,newStartVnode,oldEndVnode,newEndVnode则分别指向了before和after节点列表中对应哨兵节点下标的值,值为before[oldStartVnode],after[newStartIdx],before[oldEndIdx],after[newEndIdx]。

Diff
当哨兵满足oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx的条件的时候,我们会循环进行一系列节点之间的比较。

优先判断
我们首先对上面声明的各个节点进行一些优先级较高的判断。

判断1:oldStartVnode是否为空,若为true则oldStartIdx向后移动,继续下一个节点的判断。判断代码如下:
在这里插入图片描述
判断2:oldEndVnode是否为空,若为true则oldEndIdx向前移动。判断代码如下:
在这里插入图片描述
判断3:使用 sameVnode判断before和after未判断的头节点是否为相同节点,若为true,则按照上面思路说的,对相同类型节点进行节点的属性的更新并修改哨兵位置。
在这里插入图片描述
判断4:使用上一步相同的方法对oldEndVnode和newEndVnode进行判断。并执行相同的更新操作。
在这里插入图片描述
判断5:使用sameVNode判断旧列表的头节点和新列表的尾节点进行判断,
sameVnode(oldStartVnode, newEndVnode),若为true,更新相同节点,若该节点可以移动在真实DOM中将oldStartVnode,放到真实节点列表的最后。
在这里插入图片描述
判断6:使用sameVnode比较旧列表的尾节点和新列表的头节点,若为true,和上面一样,更新相同节点,将oldEndVnode放到真实节点列表的最开始。
在这里插入图片描述

通过这一系列的优先判断条件,一方面对于一些不需要做移动的DOM可以得到快速处理,另一方面使待处理节点变少,缩小了后续操作的处理范围,可以更快地完成同级节点的对比。

若节点不满足上面的所有判断,则会进入到最后一个条件分支,判断7:
在这里插入图片描述
循环结束
最后当oldStartIdx > oldEndIdx || newStartIdx > newEndIdx,也就是新或旧节点数组有一个被查找完之后则退出判断循环。当循环结束时,旧节点数组中剩下的节点即为要删除的节点,新节点数组中剩下的即为要新增的节点。只需要进行简单的新增和删除操作即可,代码如下:
在这里插入图片描述
经历过了这么多的判断之后,就完成了同级节点之间的Diff比较。

就地复用
在Diff中会使用到一种就地复用的策略。就地复用是指Vue会尽可能复用之前的DOM,尽可能不发生DOM的移动。

Vue判断新旧节点是否为相同节点(也就是上面的sameVnode方法),这个相同节点的意思并不是两个完全相同的节点,实际上它仅判断是否为同类节点(同类节点为类型相同且节点数据一致,如前后两个span,span标签上的属性没有改变,但是里面的内容变了,这样就算作同类节点),如果是同类节点,那么Vue会直接复用旧DOM节点,只要更新节点中的内容即可。这样可以大大减少列表中节点的移动操作。

总结
Vue中的Diff算法采用了React相似的思路,都是同层节点进行比较,在比较的过程中,使用了一些优先判断和就地复用策略,提高了Diff算法的效率。

那么在面试工程中,可以怎么组织语言呢?

答:diff算法的作用:用来修改DOM的一小段,不会引起dom树的重绘
diff算法的实现原理:diff算法将虚拟DOM的某个节点数据改变后生成新的的node节点与旧节点进行比较,并替换为新的节点,具体过程就是调用Patch方法,比较新旧节点,一边比较一边给真实DOM打补丁进行替换
简单来说,diff算法有以下过程
同级比较,再比较子节点
如果节点类型不同,直接干掉前面的节点,再创建并插入新节点,不会再比较这个节点以后的子节点。
(先判断一方有子节点一方 没有子节点的情况(如果新的children没有子节点,将旧的节点移除))
比较都有子节点的情况(核心diff)
递归比较子节点
正常diff两个树的时间复杂度是O(n^3), 但实际情况下我们很少会进行跨层级的移动DOM,所以vue将diff进行了优化,从O(n^3)–>O(n),只有当新旧children都为多个子节点时才需要用核心的diff算法进行同层级比较。
Vue2的核心diff算法采用了双端比较的算法,同时从新旧children的两端开始进行比较,借助Key值找到可复用的节点,再进行相关操作。
新旧children中的节点只有顺序是不同的时候,最佳的操作应该是通过移动元素的位置来达到更新的目的,需要在新旧children的节点中保存映射关系,以便能够在旧children的节点中找到可复用的节点。key也就是children中节点的唯一标识。
相比React的diff算法,同样情况下可以减少移动节点的次数,减少不必要的性能损耗,更加的优雅。
Vue3.x借鉴了ivi算法和inferno算法
在创建VNode时就确定其类型,以及在mount/patch的过程中采用位运算来判断一个VNode的类型,在这个基础之上再配合核心的Diff算法,使得性能上较Vue2.x有了提升(实际的实现可以结合Vue3.x的源码看)
该算法中还运用了动态规划的思想求解最长递归子序列

  • 12
    点赞
  • 49
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

老电影故事

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值