引用资料:https://segmentfault.com/a/1190000008782928
最早是react有虚拟dom,效率相比直接操作dom结构提高了N倍,这两天看了下vue的虚拟dom,现在和大家一起分享,大家如果想全部看,可以看看上面的引用资料,看得仔细的话加上自己的思维去思考,应该是可以明白diff当中的奥义的。
这里我只分享diff的算法,个人理解,欢迎交流
几个要看懂的函数:
1:patch
2:patchVnode
3:updateChildren【核心,真正对比新旧两个数组的算法】
```
updateChildren (parentElm, oldCh, newCh) {
let oldStartIdx = 0, newStartIdx = 0
let oldEndIdx = oldCh.length - 1
let oldStartVnode = oldCh[0]
let oldEndVnode = oldCh[oldEndIdx]
let newEndIdx = newCh.length - 1
let newStartVnode = newCh[0]
let newEndVnode = newCh[newEndIdx]
let oldKeyToIdx
let idxInOld
let elmToMove
let before
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
if (oldStartVnode == null) { //对于vnode.key的比较,会把oldVnode = null
oldStartVnode = oldCh[++oldStartIdx]
}else if (oldEndVnode == null) {
oldEndVnode = oldCh[--oldEndIdx]
}else if (newStartVnode == null) {
newStartVnode = newCh[++newStartIdx]
}else if (newEndVnode == null) {
newEndVnode = newCh[--newEndIdx]
}else if (sameVnode(oldStartVnode, newStartVnode)) {
patchVnode(oldStartVnode, newStartVnode)
oldStartVnode = oldCh[++oldStartIdx]
newStartVnode = newCh[++newStartIdx]
}else if (sameVnode(oldEndVnode, newEndVnode)) {
patchVnode(oldEndVnode, newEndVnode)
oldEndVnode = oldCh[--oldEndIdx]
newEndVnode = newCh[--newEndIdx]
}else if (sameVnode(oldStartVnode, newEndVnode)) {
patchVnode(oldStartVnode, newEndVnode)
api.insertBefore(parentElm, oldStartVnode.el, api.nextSibling(oldEndVnode.el))
oldStartVnode = oldCh[++oldStartIdx]
newEndVnode = newCh[--newEndIdx]
}else if (sameVnode(oldEndVnode, newStartVnode)) {
patchVnode(oldEndVnode, newStartVnode)
api.insertBefore(parentElm, oldEndVnode.el, oldStartVnode.el)
oldEndVnode = oldCh[--oldEndIdx]
newStartVnode = newCh[++newStartIdx]
}else {
// 使用key时的比较
if (oldKeyToIdx === undefined) {
oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx) // 有key生成index表
}
idxInOld = oldKeyToIdx[newStartVnode.key]
if (!idxInOld) {
api.insertBefore(parentElm, createEle(newStartVnode).el, oldStartVnode.el)
newStartVnode = newCh[++newStartIdx]
}
else {
elmToMove = oldCh[idxInOld]
if (elmToMove.sel !== newStartVnode.sel) {
api.insertBefore(parentElm, createEle(newStartVnode).el, oldStartVnode.el)
}else {
patchVnode(elmToMove, newStartVnode)
oldCh[idxInOld] = null
api.insertBefore(parentElm, elmToMove.el, oldStartVnode.el)
}
newStartVnode = newCh[++newStartIdx]
}
}
}
if (oldStartIdx > oldEndIdx) {
before = newCh[newEndIdx + 1] == null ? null : newCh[newEndIdx + 1].el
addVnodes(parentElm, before, newCh, newStartIdx, newEndIdx)
}else if (newStartIdx > newEndIdx) {
removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
}
}
```
这里的代码其实是在匹配操作新旧两个数组,如果旧数组有,新数组没有的话,最终会移除,新数组有,旧数组没有则会被插入,只是插入的位置是新数组匹配时候旧数组index的前一位,这里几幅图解释下如何匹配的更新插入删除操作的。【别人讲的都是两个数组,四个变量,相互往中间推,任何一个先操作完就算结束】
新旧数组匹配示意图
过程可以概括为:oldCh和newCh各有两个头尾的变量StartIdx和EndIdx,它们的2个变量相互比较,一共有4种比较方式。如果4种比较都没匹配,如果设置了key,就会用key进行比较,在比较的过程中,变量会往中间靠,一旦StartIdx>EndIdx表明oldCh和newCh至少有一个已经遍历完了,就会结束比较
但是我觉得这里没有讲具体怎么靠。
newStartIndex会在oldCh内部循环,从oldStartIndex到oldEndIndex,无论在oldCh内部是否找到有相同的节点,newStartIndex都会往右偏移,
这里分两种情况,匹配上和没匹配上
匹配上:直接插入到oldStartIndex节点前面,newStartIndex++,开始下一轮循环,同样从oldStartIndex到oldEndIndex.
没匹配上:直接插入到oldStartIndex节点前面,循环到oldEndIndex,newStartIndex++,开始下一轮循环,同样从oldStartIndex到oldEndIndex.
oldStartIndex++,oldEndIndex--,和newEndIndex--,会在什么情况下出现,只能出现oldStartIndex节点==newEndIndex节点或者newEndIndex节点==oldEndIndex节点或者oldEndIndex节点==newStartIndex节点,这样情况才会出现数组除了newStartIndex节点往内靠,其他节点也往内靠
结束情况,代码当中,
如果oldStartIdx > oldEndIdx,说明就数组先遍历完,新数组没有,说明新数组有多,然后需要将新数组新的给添加进去
如果newStartIdx > newEndIdx,说明新数组先遍历完,旧数组没有,说明新数组变少了,旧数组有多余,得移除掉旧数组多余的。
核心,得记住,遍历每次一直往中间靠的是newStartIndex节点,而不是所有节点
if (oldStartIdx > oldEndIdx) {
before = newCh[newEndIdx + 1] == null ? null : newCh[newEndIdx + 1].el
addVnodes(parentElm, before, newCh, newStartIdx, newEndIdx)
}else if (newStartIdx > newEndIdx) {
removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
}
网友评论