根据新旧节点的对比结果,更新DOM元素,此过程并不改变新旧节点的排序。序号指向正在处理的节点,分别是新旧节点的起始和结尾节点。对比过程以新起始节点为主导,对比方向是由两侧向中间。优先比对新旧节点的起始节点和结尾节点,再查找与新起始节点相同的且未处理的旧节点。当旧节点全部处理完(旧起始和结尾序号重叠),此时新节点可能未处理完,就添加新节点DOM元素。当新节点全部处理完(新起始和结尾序号重叠),可能存在旧节点,就删除旧节点DOM元素。
具体流程如下:
新旧子节点的起始序号不大于结尾序号时,执行以下流程:
a)如果旧子节点两侧存在undefined节点
旧起始节点undefined,oldStartVnode = oldCh[++oldStartIdx]
旧结尾节点undefined,oldEndVnode = oldCh[--oldEndIdx]
b)新旧子节点的起始节点相同(前后比较)
patchVNode更新DOM内容
oldStartVnode = oldCh[++oldStartIdx]
newStartVnode = newCh[++newStartIdx]
c)新旧子节点的结尾节点相同(前后比较)
patchVNode更新DOM内容
oldEndVnode = oldCh[--oldEndIdx]
newEndVnode = newCh[--newEndIdx]
d)旧起始节点和新结尾节点相同(前后比较)
patchVNode更新DOM内容
将旧起始节点DOM添加到旧结尾节点DOM前面
oldStartVnode = oldCh[++oldStartIdx]
newEndVnode = newCh[--newEndIdx]
e)旧结尾节点和新起始节点相同(前后比较)
patchVNode更新DOM内容
将旧结尾节点DOM添加到旧起始节点DOM前面
oldEndVnode = oldCh[--oldEndIdx]
newStartVnode = newCh[++newStartIdx]
f)其他(缓存尚未处理的旧节点key值,依此判断旧节点中是否存在和新起始节点相同的节点)
a)尚未处理的旧节点中不存在与新起始节点相同的节点
创建新节点DOM并添加到旧起始节点DOM的前面
newStartVnode = newCh[++newStartIdx]
b)旧节点中存在与新起始节点key相同的节点
a)旧节点中存在与新起始节点相同的节点
patchVode
将相同的旧节点DOM添加到旧起始节点DOM前面
将相同的旧节点置为undefinedoldCh[idxInOld] = undefined
newStartVnode = newCh[++newStartIdx]
b)key相同,但标签类型不同的节点
创建新节点DOM并添加到旧起始节点DOM的前面
newStartVnode = newCh[++newStartIdx]
循环结束
a)如果旧节点遍历完(oldStartIdx > oldEndIdx)
把剩余未处理新节点DOM添加到上一个新结尾节点DOM前面(从新起始节点到新结尾节点,都未处理过)
b)如果新节点遍历完(newStartIdx > newEndIdx)
移除旧起始和结尾节点以及他们之间的节点的DOM(从旧起始节点到旧结尾节点,可能存在处理过的节点,但处理过已被置为undefined)
function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) { var oldStartIdx = 0;// 表示当前正在处理的旧起始节点序号 var newStartIdx = 0;// 表示当前正在处理的新起始节点序号 var oldEndIdx = oldCh.length - 1;// 表示当前正在处理的旧结尾节点序号 var oldStartVnode = oldCh[0];// 表示当前正在处理的旧起始节点 var oldEndVnode = oldCh[oldEndIdx];// 表示当前正在处理的旧结尾节点 var newEndIdx = newCh.length - 1;// 表示当前正在处理的新结尾节点序号 var newStartVnode = newCh[0];// 表示当前正在处理的新起始节点 var newEndVnode = newCh[newEndIdx];// 表示当前正在处理的新结尾节点 var oldKeyToIdx, idxInOld, vnodeToMove, refElm; ... while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) { if (isUndef(oldStartVnode)) { oldStartVnode = oldCh[++oldStartIdx]; // Vnode has been moved left } else if (isUndef(oldEndVnode)) { oldEndVnode = oldCh[--oldEndIdx]; } else if (sameVnode(oldStartVnode, newStartVnode)) { patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx); oldStartVnode = oldCh[++oldStartIdx]; newStartVnode = newCh[++newStartIdx]; } else if (sameVnode(oldEndVnode, newEndVnode)) { patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx); oldEndVnode = oldCh[--oldEndIdx]; newEndVnode = newCh[--newEndIdx]; } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx); canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm)); oldStartVnode = oldCh[++oldStartIdx]; newEndVnode = newCh[--newEndIdx]; } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx); canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm); oldEndVnode = oldCh[--oldEndIdx]; newStartVnode = newCh[++newStartIdx]; } else { if (isUndef(oldKeyToIdx)) { oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx); }// 缓存尚未处理的旧节点key值 idxInOld = isDef(newStartVnode.key) ? oldKeyToIdx[newStartVnode.key] : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx); if (isUndef(idxInOld)) { // New element createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx); } else { vnodeToMove = oldCh[idxInOld]; if (sameVnode(vnodeToMove, newStartVnode)) { patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx); oldCh[idxInOld] = undefined; canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm); } else { // same key but different element. treat as new element createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx); } } newStartVnode = newCh[++newStartIdx]; } } if (oldStartIdx > oldEndIdx) { refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm; addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue); } else if (newStartIdx > newEndIdx) { removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx); } }
updateChildren的示例:
1.左边表示新旧节点,节点下面标识起始和结尾节点(即正在处理的节点)。右边表示当前的DOM。