Vue2-Diff

97 阅读4分钟

1.请你说说Vue2的Diff操作具体原理?

Diff操作一共包含三个步骤:

创建节点,更新节点,删除节点。

创建和删除节点都比较简单,这里跳过。接下来说说更新节点如何处理:

如果新的vnode和旧的vnode完全一样,跳过处理

如果新的vnode和旧的vnode都是静态节点,跳过处理

(什么是静态节点呢,静态节点就是不包括任何表达式的节点)

如果新的vnode有text属性,那么判断旧的vnode的text属性是不是更新的vnode的text属性一样,如果不一样就用新的vnode的text属性更换真实DOM的文本

如果新的vnode和旧的vnode都有子节点,那么就对比判断子节点是否都相同

如果只有新的vnode存在子节点,旧的vnode没有子节点,那么就判断旧的vnode是否有文本,如果有文本就清空文本。之后在把vnode的子节点添加到真实DOM上

如果只有旧的vnode存在子节点,新的vnode没有,那么就把DOM的子节点清空

如果新的vnode和旧的vnode都没有子节点,但是旧的vnode有文本,那么就清空旧vnode的文本

接下来就是更新子节点的具体步骤:

对比遍历的时候,会产生四种情况,分别是:创建子节点,删除子节点,移动子节点,更新子节点

创建子节点只会在所有未处理节点之前插入,不然会产生位置的问题

删除子节点操作比较简单,不在赘述

更新节点更上面的更新步骤一样,不在赘述

移动子节点会先去对比两个节点是否相同,如果相同先更新节点,之后在进行移动

虽然双层遍历对比能够解决问题,但是如果节点数量很多,时间复杂度就会很高,所以Vue又做了优化对比。

设有四个节点,分别是新前,新后,旧前,旧后

先进行新前和旧前的比较,如果是相同节点那么就直接进入更新操作,并且由于两个节点位置相同,位置无需更改

后进行新后和旧后的比较,如果是相同节点那么就直接进入更新操作,并且由于两个节点位置相同,位置无需更改

后进行新后和旧前的比较,如果是相同节点那么就直接进入更新操作,将旧前的位置移到最后一个子节点

后进行新前和旧后的比较,如果是相同节点那么就直接进入更新操作,将旧后的位置移到第一个子节点

(比较过后,新前和旧前位置的索引会往后走,旧后和新后位置的索引会往前走。如果开始的索引大于结束的索引,那么遍历就已经结束了,所有的节点都已经比对完成)

如果都不相同,那么就直接进入遍历的步骤

2.请你说说Vue3的Diff做了什么优化?

1.去除相同前置和后置元素

2.最长递增子序列

用了贪心+二分查找+前驱节点的算法,思路如下:

步骤1.先创建一个空数组result保存索引。遍历nums,将当前项current和result的最后一项对应的值last进行比较。如果当前项大于最后一项,直接往result中新增一项;否则,针对result数组进行二分查找,找到并替换比当前项大的那项。

下图示意图中为了方便理解result存放的是nums中的值,实际代码存放的是数组索引。

5.png 步骤2. 这步是难点,因为步骤1在替换的过程中贪心了,导致最后的结果错乱。

为了解决这个问题,使用的前驱节点的概念,需要再创建一个数组preIndexArr。在步骤1往result中新增或者替换新值的时候,同时preIndexArr新增一项,该项为当前项对应的前一项的索引。这样我们有了两个数组:

  • result:[1,3,4,6,7,9]
  • preIndexArr:[undefined,0,undefined,1,3,4,4,6,1]

result的结果是不准确的,但是result的最后一项是正确的,因为最后一项是最大的,最大的不会算错。我们可知最大一项是值9,索引是7。可查询preIndexArr[7]获得9的前一项的索引为6,值为7...依次类推能够重建新的result。

注意:下图中为了方便理解,result存放的是值,实际代码中存放的是索引。