在面试中,关于vue中diff算法的问题时常出现,很多人都不知道怎么去回答,那笔者在这里简单总结一下,虽然算不上是很标准的答案,但是对于回答面试官的问题应该足以应对。
注意:diff算法并非vue所专有(凡是涉及到虚拟DOM的,多半都要用到diff算法);
那么,接下来,我们从vue源码层面的三个方面进行分析:
1. 必要性
源码位置:vue/src/core/instance/lifecycle.js-----mountComponent()
mountComponent()方法中,我们就可以得到它的必要性;
我们看下面这段代码:
此时,我们必须知道一点,那就是这个mountComponent()方法是$mount在调用的;
也就是说,任何一个vue组件的实例,在创建完成之后,需要挂载的时候去调用它($mount);
那么,一个组件会调用一次$mount,也就是说,一个组件会创建一次Watch;
这里我们需要明白的是:组件中可能存在很多个data中的key使用;
通俗来讲:当data中的属性发生变化时,此时就涉及到了多个key的变化,但是,我们只有一个Watch函数,怎么能确保是哪个key发生了变化呢?
这时,diff算法的作用就体现出来了,由于Watch粒度降低,每个组件只有一个Watch,所以,必须使用diff来确保更新追踪;
2. 执行方式
diff算法的起始点:pathVnode()
源码位置:vue/src/core/vdom-----patch.js
首先,patchVnode()是diff发生的地方;
核心代码展示(由于篇幅问题,展示代码仅供参考):
这里的diff代码就和数据结构挂钩了,它的整体策略是:深度优先,同层比较(不过多阐述,......);
diff发生之后,呈现给我们的就是,数据更新之后的渲染效果;
3. 高效性
高效性就不得不提updateChildren()这个方法了,它是diff算法中最重要的方法;
其实,在上一篇文章中讲key时,我就详细提到过updateChildren()方法,它的作用主要是向下递归遍历整个文档树,从而做出更新渲染操作;
这里我就不再详细说明了,感兴趣的朋友可以自己去学习一下;
源码位置:vue/src/core/vdom-----patch.js---updateChildren()
4. 总结
1. diff算法是虚拟DOM技术的必然产物:通过新旧虚拟DOM作为对比(即diff),将变化的地方更新在真实DOM上,另外,也需要将diff高效的执行对比过程,从而降低事件复杂度O(n);
2. vue.2.x 中为了降低Watcher粒度,每个组件只有一个Watcher与之对应,只有引入diff才能精确找到发生变化的地方;
3. vue中diff执行的时刻是组件实例执行其更新函数时,它会比对上一次渲染结果oldVnode和新的渲染结果newVnode,此过程称为patch;
4.diff过程整体遵循深度优先,同层比较的策略,两个节点之间比较会根据他们是否拥有子节点或者文本节点做不同操作,比较两组子节点是算法的重点,首先,假设头尾节点可能相同做4次比对尝试,如果没有找到相同节点才按照通用方式遍历查找,查找结束再按情况处理剩下的节点,借助key通常可以非常精确找到相同节点,因此整个patch过程非常高效。