diff算法
vue引入vdom的优势
vue在已经知道哪里更新的前提下还是引入的VDOM,这是因为除了对更新的优化之外,VDOM还有其他的优势
1、VDOM将dom结构抽象出来,上层的组件同样可以抽象化具有更高的适配能力
2、VDOM的结构一致,可以跨平台渲染 如weex
3、配合diff算法进行比对更新,在最小颗粒度和对比范围找到平衡点
diff算法
https://blog.csdn.net/chenzhizhuo/article/details/101531228
平层对比减少时间复杂度n3到n,比对过程是OldVnode和vnode进行对比,以vnode的视图为结果
patchVnode()
- 如果两个节点是静态节点直接跳过对比(在编译时会进行标记)
- 如果两个节点的根节点不同 则直接进行替换,创建vnode节点插入dom,移除OldVnode的节点即可
- 两个节点相同且具有比较的价值 则进行
patchVnode(OldVnode, vnode)
的过程,否则执行上一步 - vode节点不是文本节点则比较:
1、两个节点都有子节点 且子节点不同则进行updateChild()
2、只有vnode有子节点,则进行节点创建和插入
3、只有oldNode有子节点,则进行子节点的移除,如果是文本节点则清空 - vnode是文本节点则将OldVnode节点置文本节点
- 如果vnode先结束则将OldVnode中多余的节点移除
- 如果OldVnode先结束则将vnode中多余的节点进行创建和插入
// 判断两个节点是否值得进行比对的方法是sameVnode()
function sameVnode (a, b) {
return (
a.key === b.key && // key值
a.tag === b.tag && // 标签名
a.isComment === b.isComment && // 是否为注释节点
// 是否都定义了data,data包含一些具体信息,例如onclick , style
isDef(a.data) === isDef(b.data) &&
sameInputType(a, b) // 当标签是<input>的时候,type必须相同
)
}
updateChild()
其中难点在于updateChild
的过程:
- 首先对于新旧节点和Dom视图进行标记,开始和结束索引,注意遇到
undefined
跳过即可 - 第一步进行快捷对比
1、OldStart和vStart匹配成功则都向后移动一位
2、OldStart和vEnd匹配成功则将Dom视图的OldStart移动到最后,最后将vEnd向前移动一位,OldStart后移一位
3、OldEnd和vEnd匹配成功则都向前移动一位
4、OldEnd和vStart匹配成功则将Dom视图的OldEnd移动到最前,最后将vStart后移一位,OldEnd向前移动一位 - 快捷匹配失败则进行key值匹配
1、vStart进行key值匹配成功则将Dom视图中对应的OldIndex节点移动到vStart的位置,将OldIndex的值置为undefined
,vStart后移一位
2、匹配失败则说明该节点是新的,创建并进行插入到Dom视图中对应的OldStart前即可
例题分析
- 将数组的第一个元素插入到最后[1,2,3,4]=> [2,3,4,1]
1、快捷对比元素1匹配成功,则Dom更新[1,2,3,4]=>[2,3,4,1]
2、OldStart后移 vStart前移一位,后都快捷对比成功 - [A,B,C,D]=>[E,C,A,D,B]
1、E快捷对比失效则进行Key值查找,查找失败则直接创建Dom更新为[E,A,B,C,D],vStart后移一位
2、C进行快捷查找失败,进行Key查找成功,Dom更新为[E,C,A,B,D],OldVnode中C置为undefined,vStart后移一位
3、A头匹配成功,开始节点均后移一位
4、B快捷查找成功,Dom更新为[E,C,A,D,B],vEnd前移一位,OldStart后移一位
5、OldStart遇到undefined跳过自动后移一位
6、D快捷匹配成功 后移一位,跳出循环结束
功能函数
emptyNodeAt 将一个真实的无子节点的dom节点转化为vnode形式
如:<div id='a' class='b c'></div>
将转换为{sel:'div#a.b.c',data:{},children:[],text:undefined,elm:<div id='a' class='b c'>}
sameVnode 比较两个vnode节点是否相似 相似patch 不同直接进行移除和添加
createElm 通过vnode来创建真实的dom节点,并将其赋值给vnode.elm 。递归将vnode节点构建成dom树 触发全局的create钩子 推进insertedVnodeQunue ,实现批量插入触发insert回调
removeVnodes 批量删除dom节点 配合invokeSestoryHook触发destory回调和createRmCb 对remove回调进行计数
addVnodes 将vnode转化后的dom节点插入daodom树的指定位置中
createRmCb当所有remove钩子触发完毕才会将节点从父节点移除