Vue中diff原理学习

1函数式编程与命令式编程

命令式编程关注于解题步骤,函数式编程关注于数据映射。

命令式编程是一步一步来的,如我们小学解决数据题,没有数据之间的公式,只能一步一步算,函数式编程像升了上了初中之后,开始学习公式,遇到小学那样的问题,套一套公式就出来了,无需再一步步计算。再如二叉树翻转命令式编程是需要判断节点是否为null,然后开始翻转左树,翻转幼树,最后在左右树互换,关注了解题步骤;而函数式编程考虑了二叉树翻转和原二叉树的关系,翻转二叉树和原二叉树每一个节点都递归的相反,故可以通过反向递归原二叉树得到二叉树翻转的结果,关注于原二叉树和翻转二叉树之间的映射关系。

在函数式编程思想未出现时,常用的前端编程思想是命令式编程。命令式编程需要在js中操作大量的dom,往往代码复杂难懂,不利于维护。而函数式编程sh减少了代码量,便于理解,减少了出bug的概率。

2 虚拟dom的出现

随着React、Vue这些前端框架出现,我们只需改变数据,dom操作交给框架来实现,我们由命令式编程转向了函数式编程。在未有dom的情况下,框架一发现数据更新,就要更新整个页面浏览器重复创建相同的元素,严重造成的性能的浪费。故人们想到了虚拟dom,在数据与dom中见使用类似缓存的东西,即虚拟dom,数据更新后,将新的虚拟dom和缓存的dom即旧的dom进行对比,对比发现变化,再进行局部的更新,进而提高框架的性能。

vue中采用js对象结构来模拟真实dom树结构,先将template解析为抽象语法书,在优化为虚拟dom节点,根据虚拟dom节点,生成真实dom节点。有数据改变,生成新的虚拟dom节点,和旧的dom节点进行比对,有差别则对真实dom进行操作。

3虚拟dom和直接操作真实dom效率比较

虚拟dom的出现时为了解决框架更新数据后更新视图的效率问题,是先对区别,在给真实dom打补丁,进行真实dom的操作。而我们以前使用命令式编程时,在数据更新时,直接进行真实dom的操作,确实了对比的过程,故直接操作dom的性能不会低于虚拟dom、diff算法,甚至会优于。

故性能比较:

直接操作dom > 命令式编程变为函数式编程,数据驱动框架出现虚拟dom后>命令式编程变为函数式编程,数据驱动框架未出现虚拟dom前

4 VUE数据更新双向绑定设计模式

vue数据更新使用的是发布者订阅者模式来进行更新的。数据更新后会调用set方法,set方法更新后,会调用通知方法notify,通知所有订阅者更新,订阅者就会调用patch方法进行真实dom的更新,实现数据的双向绑定。(后补vue数据双向绑定原理链接

5 diff算法优点

diff算法即为patch方法。diff算法降低时复杂度由o(n^3)到o(n)。(vue标签在进行解析时,会标注一些静态变量,diff算法进行比较时,会跳过这些静态变量)。diff算法使用深度优先遍历方法,遍历虚拟dom节点数的每一个节点。

扫描二维码关注公众号,回复: 12439809 查看本文章

diff算法只进行同层比较,不越级比较。

不值得比较,就删掉重建。

6 diff算法解析

1patch 函数

2 samevnode()函数

3 patchVnode()函数

4 updateChildren()函数

upadateChildren函数是diff算法重要的一步,它首先调用四种方法比较,头头、尾尾、头尾、尾头,来覆盖oldch和ch正序、倒叙的情况,提高了节点的可复用性。这四种比较完毕后,还不可服用的话,就用ch中当前开始节点newstartnode和oldch中未复用节点一一比对,查找是否可以复用。若可以复用,则深度比较,不可以复用,则真实dom中创建节点。

7 实例解析(操作的都是oldch中的el实例)

1 初始状态dom、oldch、ch分别如下所示:

2while循环第一次开始

oldch尾部E和ch头部E匹配,深度比较完毕后,真实dom节点中E移动到当前oldtartindex前面位置,oldch和ch下标分别移动

3 while循环第二次

四种匹配方式都没找到,则创建哈希表ABCD,将ch中当前newstartnode即C一一和哈希表比对,查到找C位于哈希表第三个,则将oldch中C设为undefined,后在真实dom中将C移动到当前oldstartVnode的实例即A前面

3 while循环第3次

四种匹配方式都没找到,则在上一次循环中创建的哈希表中,将ch中当前newstartnode即B一一和哈希表比对,查到找B位于哈希表第2个,则将oldch中B设为undefined,后在真实dom中将B移动到当前oldstartVnode的实例即A前面

3 while循环第4次

oldch中oldstartnode即A和ch中newstartnode即A对应,说明A现在位置正确故两者startindex前移,真实dom中位置不变

4 while循环第四次

四种比较和哈希表都没有匹配到,故是新元素,创建新元素插入到dom中(因当前oldstartnode为undefined,故新节点插入到真实dom最后),因为newstartindex > newendindex,故结束循环

5 结束循环

循环结束,因为ch先结束,故判断oldch中有多余的节点,在真实dom中进行删除

8 为什么不能使用index作为key值

如使用index 作为key:

当前node: {key:0,tage:‘li’,data:{text:1}} {key:1,tage:‘li’,props:{data:2}} {key:2,tage:‘li’,props:{data:3}}

翻转后node: {key:0,tage:‘li’,data:{text:3}} {key:1,tage:‘li’,props:{data:2}} {key:2,tage:‘li’,props:{data:1}}

本来应该当前dom中应该三个节点都复用的,但是由于diff算法samenode方法oldnode和newnode第一个是值得比较key相同,tag相同,都定义了data,是值得比较的的,故会深度比较oldnode的第一个节点和newnode的第一个节点,按顺序按照key值对应进行比较,故会调用更新方法,没有直接复用节点,多次更新视图,降低了性能。

当前node: {key:0,tage:‘li’,data:{text:1}} {key:1,tage:‘li’,props:{data:2}} {key:2,tage:‘li’,props:{data:3}}

删除后node:  {key:0,tage:‘li’,props:{data:2}} {key:1,tage:‘li’,props:{data:1}}

本来应该复用当前dom的后两个节点,删除第一个节点,但是由于diff算法samenode方法oldnode和newnode第一个是值得比较key相同,tag相同,都定义了data,是值得比较的,故会调用patchvnode方法深度比较他们两个,第二个oldnode和newnode节点也是值得比较的,会深度比较,最后多出当前dom的最后一个节点,进行删除,变成更新前两个节点,删除最后一个节点,这样进行又更新又删除的操作,极大的降低了效率。

参考链接

https://www.zhihu.com/question/28292740?sort=created

https://blog.csdn.net/hjc256/article/details/97135687

https://www.cnblogs.com/wind-lanyan/p/9061684.html

https://github.com/vuejs/vue/blob/a702d1947b856cf3b9d6ca5fb27b2271a78a9a5b/src/core/vdom/patch.js#L70

猜你喜欢

转载自blog.csdn.net/qq_39692513/article/details/108258833