导语 | 开发者工作中,研究代码逻辑常需要思考这个问题:数组变更后,具体变更了哪一些元素?变更的位置如何?本文作者陈碧松解析并覆写了针对数组变化的diff算法逻辑。希望本文对你有帮助。
diff方法的运行规则和前提方法
为了了解diff方法的运行规则和前提方法,首先我们通过几个图快速区别虚拟node进行深度优先和同级对比。
深度优先:
同级对比:
如上面图所示,每次vnode都是执行同级对比。(对应dom同一个父元素)
代码逻辑如下图:
第二,简单判断:`sameVnode`函数用来进行判断是否是同一个vnode元素。源代码如下:
如图所示:
这里有两个重要元素:`key` : 开发者定义的”:key”;`sel `: 元素tagName+元素id+元素class。
sel的定义源码如下:
vNode构建函数:
第三是构建索引。
逻辑如图:
如何处理元素
尽量不新增/删除dom。如图下所示:
如果是相同vnode,源码如下:
开始比较
首先会进行时间复杂度O(n)的while循环,循环条件为“遍历旧节点数组&&遍历新节点数组,谁先遍历完循环就结束”。源码如下图:
在每次的循环过程中,会有两大类判断方法:
1)首尾比较&首尾序号
逻辑:如图上所示。首先在循环遍历前标记好新,旧节点数组的开始位置和结束位置的序号:oldStartIdx、oldEndIdx、newStartIdx、newEndIdx;其次在循环遍历的过程中采用“首首比较,尾尾比较,首尾比较"。
源码如下:
如果数据为图上所示,那么根据首尾比较方法会有如下图所示结果,最终全部执行了更新操作:
2)索引比较
最坏情况,这里的时间复杂度也是O(n),即整个算法复杂度O(n)+O(n)。每次遍历的过程中可能存在"新数组节点新增/旧数组节点删除",那么前后对比就满足不了条件。这里逻辑会进入索引比较:比如这种情况:
那么循环中会执行一遍,创建旧数组的索引对象。从创建到比较的整个逻辑图如下:
这里的源码如下:
源码如下:
源码:
3)当节点遍历完之后
会存在两种情况:新数组已经遍历完,但旧数组没有遍历完成;旧数组遍历完成,但新数组没有遍历完成。故源代码的判断如下:
旧数组没有循环完成的效果如下图所示:
这里注意一个点,我们每次的节点更新会移动序号,即使被删除的节点不在一块最终也会被首尾比较算法“摞在一块”(oldStartIdx~oldEndIdx)。上图所示更加明显。源码在这里就进行批量删除:
效果如下图所示:
整体来说,有几个关键点:简单对比;创建旧数组的索引表;首位对比&首尾索引&vnode位置移动;索引添加/位移;剩余部分批量处理添加/删除。
经过前后对比&索引的过滤后,只会存在新.末尾节点!==旧节点及之前的连续的新节点(!==旧节点),所以这里也被“摞在一块”,即 (newStartIdx~newEndIdx)。源码如下。这样,整个diff的对比算法就已经走完了。核心就是:前后对比+索引。
vue3.0对于diff比较前的优化
vue3.0针对“无脑”patchVnode进行了过滤--静态类型Vnode老版的源码:
这里,我们再重复下vue2.x系列的对比更新逻辑:
新版的vue3.0增加了静态类型Vnode。如果是静态类型的vnode,直接跳过更新,修改新节点引用即可。
comment类型目前翻到它的源码也只是更改引用,源码作者加上了一行注释。
补充一下,flagment碎片类型为新增的vnode类型,即:
vue3.0的过滤判断源码如下:
数组比较的应用
由于我们想监听数组的变化,参考了diff算法覆写类似的逻辑,用来在update,add,dels时,代码层面获取操作的具体节点明细(新旧节点的位置,内容)。希望本文对你有帮助。
作者:陈碧松
出处:https://mp.weixin.qq.com/s/-8-AkMqwXJX54r6Lzbr5rA