前言
如果抛弃dom操作部分,则diff实际上就是两个数组之间的对比
按照不同情况,大致分为如下情况
前后列表不变
长列表变短列表
短列表变长列表
存在元素位置交换
公共变量
![](https://img.haomeiwen.com/i22517122/dbc26e9ecf17490a.png)
前后列表不变
依次按数组索引从前向后遍历,遇到首个不相同节点时停止。对当前新旧列表不变的情况,得到的结果应当是一致的
示例数组
![](https://img.haomeiwen.com/i22517122/6c37ce5e4eef4f5f.png)
则对于当前示例而言,遍历结束后,列表的终点索引位置必然>两列表长度,此时返回新列表即可
![](https://img.haomeiwen.com/i22517122/de40601a1c606162.png)
长列表变短列表
经过前一轮处理,列表相同的元素已然被复用
示例数组
![](https://img.haomeiwen.com/i22517122/9f670b389d520c4e.png)
由于是长变短,则此时停留的索引位置i一定<=旧列表长度,且>新列表长度
那么可以直接将新列表返回吗?答案是不能
考虑如下列表
![](https://img.haomeiwen.com/i22517122/44882270104b393c.png)
此时i停留的位置<=新列表,同时<新列表,最后一个react节点应当被复用却未被复用
故我们还需要一次从后向前遍历比对区查找可复用节点
![](https://img.haomeiwen.com/i22517122/7bf724609776f7aa.png)
当循环结束后,表面从后向前复用了可复用节点,此时的i必然>l2,且<=l1,此时需要将未被复用发旧节点从页面中卸载掉
![](https://img.haomeiwen.com/i22517122/83c5eef8895f9022.png)
短列表变长列表
示例数组
![](https://img.haomeiwen.com/i22517122/803f3dceec20fb90.png)
通过前两轮循环后,可复用的节点已经被复用,对于当前示例而言,循环结束后,i>l1且i<=l2
此时应该创建节点插入列表
![](https://img.haomeiwen.com/i22517122/5f89c5b5e53ff17d.png)
存在元素位置交换
示例数组
![](https://img.haomeiwen.com/i22517122/e3383828c95cd775.png)
此时,经过第一轮while循环,html和css已经被复用,在第二轮中vue和react是不同节点,故此时i=2,l1=l2=4
这既不满足长变短(i<=l1&&i>l2),且不满足短变长(i>l1&&i<=l2)
故可推断存在位置调换的可能性
那么怎么判断是否存在位置变化呢?
若将剩余节点名称在新列表中的位置标记出来
![](https://img.haomeiwen.com/i22517122/616fd9133ff7db85.png)
那么就可以在旧列表中查找同名的节点名称所在的位置。如果存在位置交换,则一定存在一个节点位置在新列表中的位置比对调元素的位置更小,故只需要记录下最大节点位置即可
![](https://img.haomeiwen.com/i22517122/87fce207d51f21b3.png)
目前为止,我们已经复用了新列表中的所有元素,对于新列表中没有而旧列表中又有的,我们却没有处理,故需要将其删除掉,对应图中框红位置代码,对应示例如下
![](https://img.haomeiwen.com/i22517122/eb186a26aae43e93.png)
最后,来处理位置交换
首先,如果想要交换节点,我们不仅要知道某个节点在旧列表中的位置,还要知道该节点在新列表中的位置,上一步中,我们已经使用restIndexOfNewListMap记录下来的新列表中的每一个节点位置,故现在只需要求出新列表中对应节点位置在旧列表中的位置即可
![](https://img.haomeiwen.com/i22517122/cf42f9833d5f93fb.png)
接下来就是考虑如何做交换
既然已经拿到了新列表中对应位置节点在旧列表中的位置,那直接将原列表剩余部分删除并缓存一份,另外准备个新数组,其长度为未处理的节点个数。我们依次按照对应位置填充是不是就行呢?如果是针对当前示例的话,是可以的。但如果是考虑到dom操作的话,直接替换相当于要重新创建dom元素,因此答案为no!
那么应当怎么做呢?我们知道,如果元素的相对位置不变,则不存在交换,反之亦然。因此我们需要计算出最长的上升子序列,得到该序列后,所有不在该序列中的节点,一定是发生了位置交换。换句话说,在该序列里的节点可以原地复用
则
若想获取最长的上升子序列,我们需要让序列上升的尽可能慢,如[1,3,7,4,5],如果我们在第三次直接挑选7作为子序列,则4和5将被忽略掉。因此我们希望每次在上升子序列后挑选的那个值尽可能的要小。但是也不是7直接就不挑选,而是在遍历到4或者5时,需要有一次"回退"操作
故我们维护一个数组result,则设result[i]表示长度为i的最长上升子序列末尾元素的最小值所对应的索引位置,每次我们找到一个更大的值时,更新到数组result,同时设p[i]来记录前一个最长上升序列末的索引位置
则遍历结束后,result的最后一个一定是最大序列所在的索引,此时只需要以此为出发点向p中对应位置向前找即可
![](https://img.haomeiwen.com/i22517122/1d7045186bab55b5.png)
接着处理移动,遍历剩余节点,使用当前索引位置j+i即查找到在列表中的节点,则凡是j在最长序列中的,均说明可以原地复用。对于步骤该序列中的,有两种情况:移动或新增
对于新增的判定,由于我们记录新旧位置对应索引时的初始值为0,故为0时即是
对于移动,由于已知当前节点和其相邻节点,并且已知它们对应的在旧列表中的位置,因此以相邻节点为参考位置在就列表中找合适的插入位置,如果相对位置不存在则说明在最后
![](https://img.haomeiwen.com/i22517122/1c25c2cc5b4af0ee.png)
测试
![](https://img.haomeiwen.com/i22517122/d07d313321202bab.png)
网友评论