手写mini-diff

前言

    如果抛弃dom操作部分,则diff实际上就是两个数组之间的对比

    按照不同情况,大致分为如下情况

        前后列表不变

        长列表变短列表

        短列表变长列表

        存在元素位置交换

公共变量

前后列表不变

    依次按数组索引从前向后遍历,遇到首个不相同节点时停止。对当前新旧列表不变的情况,得到的结果应当是一致的

    示例数组

    则对于当前示例而言,遍历结束后,列表的终点索引位置必然>两列表长度,此时返回新列表即可

长列表变短列表

    经过前一轮处理,列表相同的元素已然被复用

    示例数组

    由于是长变短,则此时停留的索引位置i一定<=旧列表长度,且>新列表长度

    那么可以直接将新列表返回吗?答案是不能

    考虑如下列表

    此时i停留的位置<=新列表,同时<新列表,最后一个react节点应当被复用却未被复用

    故我们还需要一次从后向前遍历比对区查找可复用节点

    当循环结束后,表面从后向前复用了可复用节点,此时的i必然>l2,且<=l1,此时需要将未被复用发旧节点从页面中卸载掉

(由于是引用类型,故无需手动返回,直接两列表diff后取oldList即可)

短列表变长列表

    示例数组

    通过前两轮循环后,可复用的节点已经被复用,对于当前示例而言,循环结束后,i>l1且i<=l2

    此时应该创建节点插入列表

存在元素位置交换

    示例数组

    此时,经过第一轮while循环,html和css已经被复用,在第二轮中vue和react是不同节点,故此时i=2,l1=l2=4

    这既不满足长变短(i<=l1&&i>l2),且不满足短变长(i>l1&&i<=l2)

    故可推断存在位置调换的可能性

    那么怎么判断是否存在位置变化呢?

    若将剩余节点名称在新列表中的位置标记出来

    那么就可以在旧列表中查找同名的节点名称所在的位置。如果存在位置交换,则一定存在一个节点位置在新列表中的位置比对调元素的位置更小,故只需要记录下最大节点位置即可

(vue中找到复用节点后会对该节点的子节点做patch更新,本示例不存在子节点,故不需要)

        目前为止,我们已经复用了新列表中的所有元素,对于新列表中没有而旧列表中又有的,我们却没有处理,故需要将其删除掉,对应图中框红位置代码,对应示例如下

     最后,来处理位置交换

    首先,如果想要交换节点,我们不仅要知道某个节点在旧列表中的位置,还要知道该节点在新列表中的位置,上一步中,我们已经使用restIndexOfNewListMap记录下来的新列表中的每一个节点位置,故现在只需要求出新列表中对应节点位置在旧列表中的位置即可

    接下来就是考虑如何做交换

    既然已经拿到了新列表中对应位置节点在旧列表中的位置,那直接将原列表剩余部分删除并缓存一份,另外准备个新数组,其长度为未处理的节点个数。我们依次按照对应位置填充是不是就行呢?如果是针对当前示例的话,是可以的。但如果是考虑到dom操作的话,直接替换相当于要重新创建dom元素,因此答案为no!

    那么应当怎么做呢?我们知道,如果元素的相对位置不变,则不存在交换,反之亦然。因此我们需要计算出最长的上升子序列,得到该序列后,所有不在该序列中的节点,一定是发生了位置交换。换句话说,在该序列里的节点可以原地复用

    则

    若想获取最长的上升子序列,我们需要让序列上升的尽可能慢,如[1,3,7,4,5],如果我们在第三次直接挑选7作为子序列,则4和5将被忽略掉。因此我们希望每次在上升子序列后挑选的那个值尽可能的要小。但是也不是7直接就不挑选,而是在遍历到4或者5时,需要有一次"回退"操作

    故我们维护一个数组result,则设result[i]表示长度为i的最长上升子序列末尾元素的最小值所对应的索引位置,每次我们找到一个更大的值时,更新到数组result,同时设p[i]来记录前一个最长上升序列末的索引位置

    则遍历结束后,result的最后一个一定是最大序列所在的索引,此时只需要以此为出发点向p中对应位置向前找即可

    接着处理移动,遍历剩余节点,使用当前索引位置j+i即查找到在列表中的节点,则凡是j在最长序列中的,均说明可以原地复用。对于步骤该序列中的,有两种情况:移动或新增

    对于新增的判定,由于我们记录新旧位置对应索引时的初始值为0,故为0时即是

    对于移动,由于已知当前节点和其相邻节点,并且已知它们对应的在旧列表中的位置,因此以相邻节点为参考位置在就列表中找合适的插入位置,如果相对位置不存在则说明在最后

(如果是dom元素的话,则只需要找到对应节点调用insertBefore即可,这里的位置交换未经过批量测试,可能会有不准)

测试

(输出diff后的oldList)

    

你可能感兴趣的:(手写mini-diff)