先上流程图
五角星最小量更新代码 updateChildren.js
import createElement from "./createElement";
import patchVnode from "./patchVnode";
// 判断是否是同一个节点
function checkSameVnode(a, b) {
return a.sel === b.sel && a.key === b.key;
}
export default function updateChildren(parentElm, oldCh, newCh) {
console.log(oldCh,newCh)
// 四个指针
// 旧前
let oldStartIdx = 0;
// 新前
let newStartIdx = 0;
// 旧后
let oldEndIdx = oldCh.length - 1;
// 新后
let newEndIdx = newCh.length - 1;
// 指针指向的四个节点
// 旧前节点
let oldStartVnode = oldCh[0];
// 旧后节点
let oldEndVnode = oldCh[oldEndIdx];
// 新前节点
let newStartVnode = newCh[0];
// 新后节点
let newEndVnode = newCh[newEndIdx];
let keyMap = null;
while(oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
console.log('♥♥♥♥♥♥♥♥♥♥')
// 首先应该不是判断四种命中,而是略过已经加了undefined标记的项
if (oldStartVnode === null || oldCh[oldStartIdx] === undefined) {
oldStartVnode = oldCh[++oldStartIdx];
} else if (oldEndVnode === null || oldCh[oldEndIdx] === undefined) {
oldEndVnode = oldCh[--oldEndIdx];
} else if (newStartVnode === null || newCh[newStartIdx] === undefined) {
newStartVnode = newCh[++newStartIdx];
} else if (newEndVnode === null || newCh[newEndIdx] === undefined) {
newEndVnode = newCh[--newEndIdx];
} else if(checkSameVnode(oldStartVnode,newStartVnode)) {
//新前和旧前
console.log(" ①1 新前与旧前 命中");
patchVnode(oldStartVnode,newStartVnode)
oldStartVnode = oldCh[++oldStartIdx]
newStartVnode = newCh[++newStartIdx]
}else if(checkSameVnode(oldEndVnode,newEndVnode)) {
//新后和旧后
console.log(" ②2 新后与旧后 命中");
patchVnode(oldEndVnode,newEndVnode)
oldEndVnode = oldCh[--oldEndIdx]
newEndVnode = newCh[--newEndIdx]
}else if (checkSameVnode(oldStartVnode, newEndVnode)) {
// 新后与旧前
console.log(" ③3 新后与旧前 命中");
patchVnode(oldStartVnode, newEndVnode);
// 当③新后与旧前命中的时候,此时要移动节点。移动 新后(旧前) 指向的这个节点到老节点的 旧后的后面
// 移动节点:只要插入一个已经在DOM树上 的节点,就会被移动
//这里的参数是旧前节点oldStartVnode,是因为新后与旧前是相同的,
//但是新后是没有真实dom节点(.elm)的,而所有旧节点都有elm,因为从patch函数开始每次修改oldVnode,都会修改相应的oldVnode.elm
parentElm.insertBefore(oldStartVnode.elm, oldEndVnode.elm.nextSibling);
oldStartVnode = oldCh[++oldStartIdx];
newEndVnode = newCh[--newEndIdx];
} else if (checkSameVnode(oldEndVnode, newStartVnode)) {
// 新前与旧后
console.log(" ④4 新前与旧后 命中");
patchVnode(oldEndVnode, newStartVnode);
// 当④新前与旧后命中的时候,此时要移动节点。移动 新前(旧后) 指向的这个节点到老节点的 旧前的前面
// 移动节点:只要插入一个已经在DOM树上的节点,就会被移动
parentElm.insertBefore(oldEndVnode.elm, oldStartVnode.elm);
oldEndVnode = oldCh[--oldEndIdx];
newStartVnode = newCh[++newStartIdx];
}else {
// 四种都没有匹配到,都没有命中
console.log("四种都没有命中");
// 寻找 keyMap 一个映射对象, 就不用每次都遍历old对象了
if (!keyMap) {
keyMap = {};
// 记录oldVnode中的节点出现的key
// 从oldStartIdx开始到oldEndIdx结束,创建keyMap
for (let i = oldStartIdx; i <= oldEndIdx; i++) {
const key = oldCh[i].key;
if (key !== undefined) {
keyMap[key] = i;
}
}
}
console.log(keyMap);
// 寻找当前项(newStartIdx)在keyMap中映射的序号
const idxInOld = keyMap[newStartVnode.key];
if (idxInOld === undefined) {
// 如果 idxInOld 是 undefined 说明是全新的项,要插入
// 被加入的项(就是newStartVnode这项)现不是真正的DOM节点
parentElm.insertBefore(createElement(newStartVnode), oldStartVnode.elm);
} else {
// 说明不是全新的项,要移动
const elmToMove = oldCh[idxInOld];
patchVnode(elmToMove, newStartVnode);
// 把这项设置为undefined,表示我已经处理完这项了
oldCh[idxInOld] = undefined;
// 移动,调用insertBefore也可以实现移动。
parentElm.insertBefore(elmToMove.elm, oldStartVnode.elm);
}
// newStartIdx++;
newStartVnode = newCh[++newStartIdx];
}
}
//循环结束
if(newStartIdx <= newEndIdx) {
// 说明newVndoe还有剩余节点没有处理,所以要添加这些节点
// 需要在patchVNode.js中,最后新增一行代码:newVNode.elm = oldVNode.elm。 这样才能正确找到标杆
const before = newCh[newEndIdx + 1] === undefined ? null : newCh[newEndIdx + 1].elm;
// console.log(newCh[newEndIdx + 1].elm === oldCh[0].elm) //true 父子节点的elm是同一个
for (let i = newStartIdx; i <= newEndIdx; i++) {
parentElm.insertBefore(createElement(newCh[i]),before)
}
}else if (oldStartIdx <= oldEndIdx) {
// 说明oldVnode还有剩余节点没有处理,所以要删除这些节点
for (let i = oldStartIdx; i <= oldEndIdx; i++) {
if (oldCh[i]) {
parentElm.removeChild(oldCh[i].elm);
}
}
}
}
要注意在四种命中都失败时,while循环未结束,需要继续将newStartIdx指针往下移,并且建立旧结点的keymap(简化遍历,便于寻找)
当能在旧结点中找到时,说明此节点是需要移动的,需要移动到前的前面
当并没有找到时,说明是全新的节点,需要createElement将此新节点创建成dom节点并且插入到旧前的前面
每次更新只发生在旧节点的DOM上(oldVnode.elm),也就是在已经创建好的dom树上根据虚拟节点的变化不断进行插入删除等更新
代码地址: github.com/Zoushen6/st…