【源码级】diff算法底层-及key的作用

今天花了蛮久好好研究了下虚拟dom及diff算法的源码,总结了多方资料,并自己将整个逻辑顺了一下,希望能有一点点帮助到大家的地方。

一、先讲讲diff
其实它也是虚拟dom技术的一个必然产物,并非vue专用,其他框架,只要涉及到虚拟dom,都有diff算法。因为如果我们写页面时,只修改其中的一小部分,但如果直接渲染到真实dom上,又回流又重绘的,开销很大,而可爱的diff!可以帮我们只修改我们更新的那一小块。而key则可以帮我们高效的去更新虚拟dom。key的作用在后文跟大家讲。

二、vue中的diff。
主要从diff的必要性执行高效性这三大模块进行剖析。

1、必要性---我们为什么需要diff算法?

首先我们可以看到源码lifecycle模块的mountedcomponent的那个方法。


一个组件执行一次$mount,就会创建一个watcher(watcher是和组件实例一一对应)。但组件里很可能有多个data中都使用key,但vue2一个组件只有一个watcher,为了更精确的知道在更新过程中谁变化了,所以我们需要diff。(先简单将diff概括为新旧两次虚拟dom比较,可以比出变化的地方,给大家顺个思路~)


2、执行
我们看源码 ,patchVnode,这也是diff发生的地方。patchVnode函数 是用于比较两个相同节点的子级。
发生时机:组件的实例执行更新函数的时候。


整体策略:深度优先,同层比较。

通俗的讲,先找孩子,所有孩子都比完了,才会开始去比较改节点的下一个同层级节点-找不同。
我在网上找了个图,希望可以帮助大家理解。请大家看,大家可以参考下执行顺序


整体逻辑:从源码中看,主要就是看双方是否都有孩子,若都有孩子,比孩子,也就是updatechildren函数(其实是一种递归);又分了一些情况,若一方有孩子,若都没有等等,请大家看源码:



之于以上,简单总结一下上面两张源码:

比较两个虚拟dom树,对根节点root进行执行patchvnode(比对oldVnode,newVnode)函数,比较两个根节点是否是相同节点。如果不同,直接替换(新增新的,删除旧的)。

如果相同,继续patchVnode,进一步比较属性,文本,子节点。进行更新,也就是增删改。只有当都存在子节点时,并且oldVnode === newVnode 为false时。(插一句:因为如果全等,代表子级肯定也完全相等,这还比较啥,不用比较了)会执行updateChildren函数,去进一步比较他们的子节点

3、高效性-----比孩子---updateChildren函数-----深度对比新旧节点的子节点

先假设头尾相同做四次比较(新旧的开始和结束来回重组),没找到就按照通用的方式遍历查找,查找结束,按情况在处理剩下的。。
(在头尾比较时)用key可以很精准的找到相同节点,从而找到准确的位置插入新节点。patch过程会很高效
如果不加key,永远认为是相同的节点,能做的操作只有强硬更新,避免不了频繁的更新过程,多很多dom操作,性能差。

总之,体现的就是一个循环递归:
patch —> patchVnode —> updateChildren —> patchVnode —> updateChildren —> patchVnode…

最后插一句关于key的:
key是vnode唯一的标识,且是不可变的。比如在我们码代码的时候,设置学号、id、身份证号这些唯一的。

key的作用是为了更高效的更新虚拟dom(提升渲染效率),也就是diff算法。比如我们想在bc中间加一个D,key可以做节点的唯一标识,告诉diff在更改前后是同一个节点。找到正确的位置插入新节点。
注意不要用index,因为比如中间插入数据,导致数据流标号的更改,index就变了,不仅会降低key的效率。严重的话会直接渲染出错。

猜你喜欢

转载自blog.csdn.net/m0_71981318/article/details/125539115
今日推荐