Vue3源码系列之diff算法(一文彻底搞懂)

779 阅读5分钟

前言

我们上回说到,当老的是数组,新的也是数组就会进行diff算法

第一步

Image.png

将老的儿子和新的儿子以及比较的是哪个元素

第二步

Vue3并没有采用双指针,都是默认从头开始比对

情况1

Image.png

Image.png

  • 先定义一个变量,值设置为0,因为一开始都是从头开始比较
  • 然后分别获取老的儿子的长度,和新的儿子的长度
  • 写一个while循环,当i小于新的和老的任意一个长度就进行diff
  • 循环内部分别取出对应儿子节点,然后判断新的和老的是否是同一个节点
  • 如果一样,则调用patch深度比较他们的儿子,patch内部又会做判断,如果是元素又会调patchElement去进行比对,递归比对,又重复了前面的流程,更新props、判断是文本还是数组等,前后都是数组又进入了diff...
  • 如果不一样就break出去,去比对下一对。这样的话我们上述图中,a和b是不是就不用管了,他们自己内部再去patch,我们本次只需要关心c、d和e去了,我们就大大缩小了比对的范围

情况2

Image.png

这个时候我们应该比对的范围是前面仨,a、d和e,这个时候我们反着比就可以了

Image.png

在上个while循环下面,我们继续再写个while循环,条件跟上一个循环是一样的

  • 从尾开始取
  • 然后判断是不是同一个节点,如果是进入patch,否则break
  • 经历了第一个和第二个循环,中间的完全不同的就晒出来了

情况3

有一方已经比对完了,多的进行挂载

Image.png

Image.png

那我们怎么确定需要挂载呢?

观察两张图,得出结论,i如果比e1大就说明需要挂载,也就是i超过就需要挂载

那我们怎么确定需要挂载的是谁呢?

观察两张图,得出结论,i和e2之间的就需要插入

那我们怎么挂载呢?

调用patch方法,当patch方法第一个参数是null,也就是没有初始虚拟节点的时候,就表示插入操作

那我们怎么知道是向前插入还是向后插入呢?

我们可以去拿到e2的下一个值,如果下一个值比自己数组长度小,则说明是需要向前插入,取下一个所对应的作为参照物,向前追加。否则,是需要向后插入,那参照物设置为null,表示直接向后追加。

老的少,新的多

Image.png

我们拿到了参照物后,传入patch,如果参照物有值,会插到它的前面,否则直接向最后追加进去,patch这里我们之前博客有写

老的多,新的少,需要卸载

这又会出现两种情况,卸载前面的还是后面的

Image.png

Image.png

我们怎么知道要删除呢?

如果i比e2大,就表示要进行删除操作,删除i和e1之间元素

第三步

  • 乱序比较,前后完全没有关系
  • 需要尽可能的复用,用新的元素做成一个映射表去老的里面找,一样的就复用,不一样的要不就插入,要不就删除

Image.png

中间情况就是乱序需要比较的地方

Image.png

Image.png

  • 创建s1和s2
  • 把新的做一个映射表,遍历s2和e2里面的元素
  • 把里面的每一个放到map里
  • 去老的里面查找,看有没有复用的,老的区间是s1-e1
  • 如果老的在新的映射表里找不到,说明,老的里的不在新的中,就把老的删掉
  • 如果找到了,就比对两个
  • 上述乱序完毕之后还有两个问题,一个是是位置问题,一个是新增节点的插入问题

第四步

我们来分析乱序比对中的两个问题,位置移动和新增插入,因为乱序中如果两个匹配上了,我们只是做了比对,并没有移动,并且也没有处理新增的情况

Image.png

Image.png

Image.png

  • 我们用一个数组把新的中需要比对的个数存一下,也就是e2-s2+2
  • 我们需要把新的索引和老的索引做一个映射,生成一个数组,初始值设置为0
  • 我们每次patch过的改成0,最后数组中是0的就是没patch的
  • 在新老比对的时候,去赋值处理
  • newIndex - 前面的ab不属于乱序中的,也就是s2
  • 然后我们就可以通过这个数组知道没有被patch的元素了,也就是新增的元素
  • 倒序循环数组,从后开始取,这个i要加上s2,这样才是真实的位置
  • 如何插呢?找到下一个元素,如果下一个元素存在,就插到它的前面,如果不存在是null,则直接插入,这个思想在前面也有用。我们这里通过当前下标jia1,来和整个length比对
  • 然后我们从数组中取出中间的元素,看看是不是0,如果是0,则是没有被patch过
  • 调用patch方法,第一个参数是null表示新增,把容器和参照物传入
  • 然后如果被patch过,直接插入,以下一个作为参照物
  • 倒序插入

可能有人有疑问,我们在哪里复用老的元素了?

在新老比对的时候,老的节点在新的里面,就会进行patch操作,这个patch操作就是复用,更新属性,比较儿子

优化

我们上述操作需要将节点全部的移动一遍,我们希望尽可能少的移动,我们只需要找联系最长的,只需要动不连续的,这就是最长递增子序列

下回我们开始分析最长递增子序列

如果您觉得有所收获,麻烦动动小手点个攒,谢谢啦~ 预知后事如何,且听下回分解~