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中的值,实际代码存放的是数组索引。
步骤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存放的是值,实际代码中存放的是索引。