这是我参与「 第四届青训营 」笔记创作活动的第四天
diff 算法更新的情况:
- 老的是 Array,新的是 Text
- 老的是 Text,新的是 Text
- 老的是 Text,新的是 Array
- 老的是 Array,新的是 Array
前三种情况比较简单
- 把旧的全部删掉更新为 Text
- 直接更新
- 删掉旧的文本,更新为新的 Array 元素
- 主要的 diff 算法发生的地方
React 的 diff 算法
深度优先,有子节点,就遍历子节点,没有子节点,就找兄弟节点,没有兄弟节点,就找叔叔节点,叔叔节点也没有的话,就继续往上找,它爷爷的兄弟,如果一直没找到,就代表所有的更新任务都更新完毕了。
步骤:
- 第一轮,从左向右新老节点进行比对查找能复用的旧节点,如果有新老节点比对不成功的,则停止这一轮的比对,并记录了停止的位置。
- 如果第一轮比对,能把所有的新节点都比对完毕,则删除旧节点还没进行比对的节点。
- 如果第一轮的比对,没能将所有的新节点都比对完毕,则继续从第一轮比对停止的位置继续开始循环新节点,拿每一个新节点去老节点里面进行查找,有匹配成功的则复用,没匹配成功的则在协调位置的时候打上 Placement 的标记。
- 在所有新节点比对完毕之后,检查还有没有没进行复用的旧节点,如果有,则全部删除。
Vue3 的 Diff 算法(双端对比算法)
步骤:
- 先从左往右进行比对,如果是相同的就进行更新比对,如果不相同则停止比对,并且记录停止的下标。 再从右往左进行比对,如果是相同的就进行更新比对,如果不相同也停止比对,也进行记录停止的下标。
- 左右比对完之后,如果新节点已经比对完了,老节点列表还存在节点未比对,则删除老节点列表上的未比对的节点,如果老节点已经比对完了,新节点列表还存在未比对的节点则进行创建。
- 如果新节点未比对完,老节点也未比对完,先把剩下的新节点处理成节点的 key 为 key, 节点下标为 value 的 Map; 接着初始化一个长度为剩下未比对的新节点的长度的数组 newIndexToOldIndexMap,初始化每个数组的下标的默认值为 0。
- 循环剩下的旧节点,查找旧节点有没有在新节点中,进行对应的更新、增加或删除
Vue2 的 Diff 算法
步骤:通过循环,每循环到一个新节点,就去老节点列表里面找到和当前新节点相同的旧节点。如果在旧节点列表中找不到,说明当前节点是需要新增的节点,我们就需要进行创建节点并插入视图的操作;如果找到了,就做更新操作;如果找到的旧节点与新节点位置不同,则需要移动节点等。循环结束后,新节点剩余的做插入操作;旧节点剩余的做删除操作。
vue2 diff 算法优化策略(快速查找节点):以下四种都找不到时,再进行循环查找
- 老数组的开始与新数组的开始
- 老数组的结尾与新数组的结尾
- 老数组的开始与新数组的结尾
- 老数组的结尾与新数组的开始
React、Vue3、Vue2 的 Diff 算法对比
相同点
-
在进行更新 Diff 对比的时候,都是优先处理简单的场景,再处理复杂的场景
- React 中是先处理左边部分,左边部分处理不了,再进行复杂部分的处理;
- Vue2 则先进行首尾、首首、尾尾部分的处理,然后再进行中间复杂部分的处理;
- Vue3 则先处理首尾部分,然后再处理中间复杂部分
- Vue2 和 Vue3 最大的区别就是在处理中间复杂部分使用了最长递增子序列算法找出稳定序列的部分
-
在处理老节点部分,都需要把节点处理 key - value 的 Map 数据结构,方便在往后的比对中可以快速通过节点的 key 取到对应的节点。同样在比对两个新老节点是否相同时,key 是否相同也是非常重要的判断标准。所以不同是 React, 还是 Vue,在写动态列表的时候,都需要设置一个唯一值 key,这样在 diff 算法处理的时候性能才最大化。
-
在移动或者创建节点的时候都使用了
insertBefore(newnode,existingnode)这个 API:- newnode 必需。需要插入的节点对象。
- existingnode 可选。在其之前插入新节点的子节点。如果未规定,则 insertBefore 方法会在结尾插入 newnode
不同点
-
对静态节点的处理不一样
-
Vue 是通过 template 模版进行编译的,所以在编译的时候可以很好对静态节点进行分析然后进行打补丁标记
- 在 Diff 的时候,Vue2 是判断如果是静态节点则跳过过循环对比
- Vue3 则是把整个静态节点进行提升处理,Diff 的时候是不进入循环的(所以 Vue3 比 Vue2 的 Diff 性能更高效)
-
React 因为是通过 JSX 进行编译的,是无法进行静态节点分析的
-
-
Vue2 和 Vue3 的比对和更新是同步进行的,这个跟 React15 是相同的,就是在比对的过程中,如果发现了那些节点需要移动或者更新或删除,是立即执行的。React16 起就更改为 比对和更新是异步进行的,所以 React16 以后的 Diff 是可以中断,Diff 和任务调度都是在内存中进行的,所以即便中断了,用户也不会知道
-
Vue2 和 Vue3 都使用了双端对比算法,而 React 的 Fiber 由于是单向链表的结构,所以在 React 不设置由右向左的链表之前,都无法实现双端对比
为什么 Vue 中不需要使用 Fiber
- 首先时间分片是为了解决 CPU 进行大量计算的问题,因为 React 本身架构的问题,在默认的情况下更新会进行过多的计算,就算使用 React 提供的性能优化 API,进行设置,也会因为开发者本身的问题,依然可能存在过多计算的问题。
- 而 Vue 通过响应式依赖跟踪,在默认的情况下可以做到只进行组件树级别的更新计算,而默认下 React 是做不到的(据说 React 已经在进行这方面的优化工作了),再者 Vue 是通过 template 进行编译的,可以在编译的时候进行非常好的性能优化,比如对静态节点进行静态节点提升的优化处理,而通过 JSX 进行编译的 React 是做不到的。
- React 为了解决更新的时候进行过多计算的问题引入了时间分片,但同时又带来了额外的计算开销,就是任务协调的计算,虽然 React 也使用最小堆等的算法进行优化,但相对 Vue 还是多了额外的性能开销,因为 Vue 没有时间分片,所以没有这方面的性能担忧。
- 根据研究表明,人类的肉眼对 100 毫秒以内的时间并不敏感,所以时间分片只对于处理超过 100 毫秒以上的计算才有很好的收益,而 Vue 的更新计算是很少出现 100 毫秒以上的计算的,所以 Vue 引入时间分片的收益并不划算。