Vue2、Vue3 Diff 理解总结

229 阅读4分钟

前言

该文仅是个人对于vue diff的理解和总结,还是建议大家去研究一下vue2、vue3 diff的源码,其中细节不用刻意去记忆,但是对于理解vue大有裨益。

Vnode

在vue中,会根据真实dom结构来模拟出一份数据(Vnode),在一段时间内,对于dom的操作都会先在Vnode上体现,最终我们会得到两份数据,即new Vnode和 old Vnode。

Diff

new Vnodeold Vnode进行对比,争取尽可能多的复用节点,以争取用最小的更新代价来渲染出新的页面。
说起diff算法,个人认为它们和奥数倒有些相似,对于一些满足特殊规则的片段,能用特殊的方法快速得到结果。所以react diff、vue2 diff、vue3 diff它们都是在各自的特殊处理上有所不同罢了,但殊途同归,它们都是为了解决能更快速的完成对比并且更多的复用。

key

既然有diff对比,那么它必然需要一个唯一的键来作为标识,这便是我们经常提到的key了。可以尝试思考下这两个问题:
1.为什么在有些场景下,不建议使用下标作为for循环节点key属性值。
2.为什么通过手动改变组件的key属性,可以达到刷新组件,重新渲染的目的。

Diff总是有必要吗?

diff存在的动机肯定是要优化渲染性能(快速的比对以及尽可能的复用节点),这是毋庸置疑的,但是,它总是会起优化作用吗?或者说,在什么情况下,对比新旧Vnode这个过程显的没有意义?

例子一:
old Vnode: [ 1, 2, 3, 4, 5, 6, 7, 8 ]
new Vnode: [ 1, 2, 5, 4, 3, 6, 7, 8 ]
通过比对,发现只是 "3"和"5"的位置调换了,那么我们只需要去操作"3"和"5"就可以了。

例子二:
old Vnode: [ 1,2,3,4,5,6,7,8 ]
new Vnode: [ 10,11,12,.....]
通过比对,我们会对1做删除操作,10做新增操作,以此类推...,(当然这个过程也会是借助文档碎片最终批量完成)

但是我们通过肉眼一下就能看出,new Vnode全是新增,old Vnode全是删除,根本不需要进行比对。省下比对这个时间不香吗(vue3借助编译器对此做了优化,很nice)。其实在vue2中,当父级发生改变后,便不会去对比子集了,会直接用新的,由此也能看出,避免无意义的比较,节省diff消耗的过程是很有价值的

以上,并不是想说vue的diff机制不好,而是从辩证的角度来分析diff的过程,会对理解它更有帮助。

vue2 diff

1:先进行双端对比,在该环节,可以快速对比出首尾相同、首尾置换的节点
2:再进行循环对比剩余节点

vue3 diff

1:寻找最长递增子序列,在该环节,可以快速对比出,一段最长连续没有变化的节点集合
2:再进行循环对比剩余节点

总结

除了在策略上的变化外,vue2 diff是全程用的数组,vue3 diff 在过程中借助了map,从数据结构上,map的读取速度也更快。

我们现在重提先前说过的“避免无意义的比较操作”,一般来说有两个办法:
1.足够好的算法,完美的循环次数。
2.精简的数据量,从数据上尽可能减少本不需要被处理“无意义数据”。

vue3编译阶段做足了功夫,在编译时,就会对我们的template做出标记
1.比如静态节点是一万年不会变的,会被打上staict的标记,在diff时便不会对他们进行比对。
2.比如用v-if限制的节点块,内部节点也无需对比,当v-if依赖的值发生变化时,肯定是整块都会变化,直接整块替换就好,这就是vue3提出的“”的概念
3.忘记了。。。有兴趣的可以去看鱿鱼须本人讲解vue3的视频,其中有大量篇幅讲解了,vue编译器是如何工作的。

以上,便是个人对vue2、3 diff的理解,个人认为vue3通过编译器上的优化做法,曲线救国,比vue3 diff算法上的改进带来的优化要高很多,这也给我带来了很多启发,在设计 项目架构时,有时候也可以在“项目本身之外”想一些办法,比如通过编译器插件、命令行程序、浏览器插件来给开发团队赋能。

关于diff代码细节的讲解,大家可以看这篇文章我也收藏了,讲的很详细。