前言
我们上回说到,当老的是数组,新的也是数组就会进行diff算法
第一步
将老的儿子和新的儿子以及比较的是哪个元素
第二步
Vue3并没有采用双指针,都是默认从头开始比对
情况1
先定义一个变量,值设置为0,因为一开始都是从头开始比较
然后分别获取老的儿子的长度,和新的儿子的长度
写一个while循环,当i小于新的和老的任意一个长度就进行diff
循环内部分别取出对应儿子节点,然后判断新的和老的是否是同一个节点
如果一样,则调用patch深度比较他们的儿子,patch内部又会做判断,如果是元素又会调patchElement去进行比对,递归比对,又重复了前面的流程,更新props、判断是文本还是数组等,前后都是数组又进入了diff...
如果不一样就break出去,去比对下一对。这样的话我们上述图中,a和b是不是就不用管了,他们自己内部再去patch,我们本次只需要关心c、d和e去了,我们就大大缩小了比对的范围
情况2
这个时候我们应该比对的范围是前面仨,a、d和e,这个时候我们反着比就可以了
在上个while循环下面,我们继续再写个while循环,条件跟上一个循环是一样的
从尾开始取
然后判断是不是同一个节点,如果是进入patch,否则break
经历了第一个和第二个循环,中间的完全不同的就晒出来了
情况3
有一方已经比对完了,多的进行挂载
那我们怎么确定需要挂载呢?
观察两张图,得出结论,i如果比e1大就说明需要挂载,也就是i超过就需要挂载
那我们怎么确定需要挂载的是谁呢?
观察两张图,得出结论,i和e2之间的就需要插入
那我们怎么挂载呢?
调用patch方法,当patch方法第一个参数是null,也就是没有初始虚拟节点的时候,就表示插入操作
那我们怎么知道是向前插入还是向后插入呢?
我们可以去拿到e2的下一个值,如果下一个值比自己数组长度小,则说明是需要向前插入,取下一个所对应的作为参照物,向前追加。否则,是需要向后插入,那参照物设置为null,表示直接向后追加。
老的少,新的多
我们拿到了参照物后,传入patch,如果参照物有值,会插到它的前面,否则直接向最后追加进去,patch这里我们之前博客有写
老的多,新的少,需要卸载
这又会出现两种情况,卸载前面的还是后面的
我们怎么知道要删除呢?
如果i比e2大,就表示要进行删除操作,删除i和e1之间元素
第三步
乱序比较,前后完全没有关系
需要尽可能的复用,用新的元素做成一个映射表去老的里面找,一样的就复用,不一样的要不就插入,要不就删除
中间情况就是乱序需要比较的地方
创建s1和s2
把新的做一个映射表,遍历s2和e2里面的元素
把里面的每一个放到map里
去老的里面查找,看有没有复用的,老的区间是s1-e1
如果老的在新的映射表里找不到,说明,老的里的不在新的中,就把老的删掉
如果找到了,就比对两个
上述乱序完毕之后还有两个问题,一个是是位置问题,一个是新增节点的插入问题
第四步
我们来分析乱序比对中的两个问题,位置移动和新增插入,因为乱序中如果两个匹配上了,我们只是做了比对,并没有移动,并且也没有处理新增的情况
我们用一个数组把新的中需要比对的个数存一下,也就是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操作就是复用,更新属性,比较儿子
优化
我们上述操作需要将节点全部的移动一遍,我们希望尽可能少的移动,我们只需要找联系最长的,只需要动不连续的,这就是最长递增子序列
下回我们开始分析最长递增子序列
如果您觉得有所收获,麻烦动动小手点个攒,谢谢啦~ 预知后事如何,且听下回分解~