Vue3中diff算法的优化和乱序比对的实现-详细步骤(包看包会)

接上回说到,当老的虚拟节点的儿子是一个数组类型,新的虚拟节点的儿子也是一个数组类型,那么就会进行diff比较

image.png

我们现在来写patchKeyedChildren

有Key情况下,先从头部比较

Vue3中diff算法的优化和乱序比对的实现-详细步骤(包看包会)_第1张图片

  • 我们先创建四个指针,由于c1和c2都是从0开始的,所以它俩的头指针我们可以用一个,然后c1尾指针是e1,c2的尾指针是c2

image.png

  • 然后进行循环,有任何一方停止循环则直接跳出
  • 判断c1项和c2项是否是同一个节点,如果是,那我们就递归进行比较,调用patch,patch里再去比较它们的属性、子节点等
  • 每比较完一次,移动指针

Vue3中diff算法的优化和乱序比对的实现-详细步骤(包看包会)_第2张图片

  • 这样我们就只比较一样的,这是一种特殊比较,我们先做特殊比较的原因是,尽可能的减少比较

我们从头比完之后,再从尾开始比

Vue3中diff算法的优化和乱序比对的实现-详细步骤(包看包会)_第3张图片

然后我们进行同序列挂载,同序列挂载的意思是,按照当前比较方向,把剩余的节点挂载到旧节点上。那么我们刚才从头比较和从尾比较,同序列挂载就有两种情况

Vue3中diff算法的优化和乱序比对的实现-详细步骤(包看包会)_第4张图片

Vue3中diff算法的优化和乱序比对的实现-详细步骤(包看包会)_第5张图片

那么问题来了,我们怎么知道按照当前比较方向,需要进行挂载呢?

Vue3中diff算法的优化和乱序比对的实现-详细步骤(包看包会)_第6张图片

  • 答案就是从头比较时,判断e1和i的大小,i比e1大,那肯定需要把后面的节点进行挂载
  • 当从尾开始比较时,当i比e1大的时候,i和e2之间的部分就是新增的部分
  • 两种情况是同一个规律,i比e1大,同时i和e之间的部分就是新增的部分

那么我们还需要处理是插入后面,还是前面

Vue3中diff算法的优化和乱序比对的实现-详细步骤(包看包会)_第7张图片

  • 判断c2后面有没有值,如果没值,说明是从头比较,后面的部分查到旧节点尾部
  • 同理,如果有值,往前插
  • 那么我们可以采取参照物的方式进行插入,如果参照物是null,默认插到尾部,如果参照物存在,就插到参照物的前面

处理卸载的流程

Vue3中diff算法的优化和乱序比对的实现-详细步骤(包看包会)_第8张图片

Vue3中diff算法的优化和乱序比对的实现-详细步骤(包看包会)_第9张图片

  • 与挂载的流程正好相反
  • 当i比e2大,说明需要卸载,卸载的范围是i到e1之间的就是需要卸载的

总结特殊情况的处理:

当前我们处理了几种特殊情况,从头比较,从尾比较,同序列需要挂载的情况,同序列需要删除的情况,我们处理这几种特殊情况的目的是后续能够尽可能少的比较,因为一些特殊的我已经先比完了,特殊情况我已经提前处理了。便于后续进行比较,这是一种优化手段。后续我们就要进行乱序比较。

乱序比对的实现

Vue3中diff算法的优化和乱序比对的实现-详细步骤(包看包会)_第10张图片

  • 头,尾连续相同的部分我们之前已经处理过了,这种情况是更加一般的情况,不符合同序列挂载、删除的情况。那么我们对这种情况里中间部分就进行乱序比对,图中是头、尾比较后,四个指针停留的位置

Vue3是将新的虚拟节点的中间部分做一个映射表

image.png

  • 做一个映射表,新的中间部分节点的key和它的索引的映射表

循环老的元素,看新的里面有没有,如果有说明要比较差异,没有要添加到列表中,老的有新的没有就要进行删除

Vue3中diff算法的优化和乱序比对的实现-详细步骤(包看包会)_第11张图片

  • 循环拿到老的元素节点中的key,去新的映射表里找
  • 如果新的里没有,就删除这个老节点,如果有就进行patch对比

问题:我们并没有移动位置,所以我们还需要处理顺序问题

Vue3中diff算法的优化和乱序比对的实现-详细步骤(包看包会)_第12张图片

Vue3中diff算法的优化和乱序比对的实现-详细步骤(包看包会)_第13张图片

  • 记录中间部分新元素的个数
  • 找到新元素中最后一项的下标
  • 找到最后一项
  • 找到最后一项的下一项,将此项作为参照物anchor
  • 当前项可能没有el,我们使用一个数组来记录是否被比对过
  • 这个数组的key记录着比对的元素在新的里面的下标,value记录着比对的元素在老的里面的下标,这个数组值是0的项,说明在新的里面没有
  • 然后循环新的总个数,判断当前循环i在这个数组里的值是不是0,如果是0,说明没有比对过,要进行创建操作,如果不是0,说明已经比对过了,进行插入
  • 这个插入过程是一个倒序插入,这个参照物很重要,一开始是f,看上面图,然后随着遍历过程参照物不断往前

总结乱序比对:

  • 首先遍历新虚拟dom中的中间部分,用一个Map来存储它们每一个的key和它们当前的下标,做一个映射表
  • 然后循环老的虚拟dom中间的元素,拿到它们的dom上的key去刚才的映射表里找,如果找不到,说明在新的虚拟dom中,没有这个老的元素,直接删掉,如果找到了,就进行比对,这次循环我们只做了两件事情,删除老的虚拟节点中多余的元素,比对新老重复的元素,但只是比对的,dom中元素的顺序并没有改变,所以我们还没有看到更新后的结果
  • 首先,我们根据新虚拟dom的中间部分的个数创建一个数组,每一项都填充0,然后在刚才的比对过程中,进行patch比对操作前记录下比对的元素在新的位置下标和在老的位置中的下标,这个很重要
  • 然后循环新节点的个数,从后往前遍历,拿到当前新元素以及后一项作为参照物,看下当前下标在刚才的数组里对象的值是否等于0,如果等于0,说明在刚才过程中,没有被比对过,那么我就创建这个元素,插入到参照物的前面,如果不等于0,那么说明刚才比对过,我们就直接复用节点插入到参照物的前面,这个参照物在这个过程中是不断往前的。这个过程结束后,我们就完成了乱序比对过程。也就说遍历新虚拟节点中间的部分,从最后一个开始往前遍历节点,如果该节点被比对过,我们就直接复用节点,否则就创建节点,然后不断的从后往前插入

最后问题

我们刚才乱序比对的过程中呢,cd其实顺序是没有动的,但是我们还是一个一个的找,然后进行插入,每一项我们都做了倒序插入,其实我们刚才可以根据刚才的数组,找到最长的递增子序列,然后不动它,其它的再进行插入。这是一种优化,因为在移动元素的时候会有性能浪费。这就是我们下一篇要实现的,实现最长递增子序列

你可能感兴趣的:(前端,linux,运维)