function patch (oldVnode, newVnode) {
// 判断oldVnode 是不是虚拟节点
if (oldVnode.sel == "" || oldVnode.sel == undefined) {
oldVnode = vnode(oldVnode.tagName.toLowerCase(), {}, undefined, undefined, oldVnode)
}
// 判断oldVnode和newVnode是不是同一节点
if (sameVnode(oldVnode, newVnode)) {
console.log("新旧节点是同一个,进行精细化比较")
patchVnode(oldVnode, newVnode)
} else {
console.log("暴力删除旧节点,插入新节点")
let newDom = createElement(newVnode)
oldVnode.elm.parentNode.insertBefore(newDom, oldVnode.elm)
// 删除旧节点
oldVnode.elm.parentNode.removeChild(oldVnode.elm)
}
}
function sameVnode(oldVnode, newVnode) {
return oldVnode.sel == newVnode.sel && oldVnode.key == newVnode.key
}
function patchVnode(oldVnode, newVnode) {
if (oldVnode === newVnode) return
if (newVnode.text != "" && (newVnode.children == undefined || newVnode.children.length == 0)) {
// 新节点有text
oldVnode.elm.innerText = newVnode.text
} else {
// 新节点有children
if (Array.isArray(oldVnode.children) && oldVnode.children.length) {
// 新节点和老节点都有children
console.log("新节点和老节点都有children")
updateChildren(oldVnode.elm, oldVnode.children, newVnode.children)
} else {
// 新节点有children 老节点为文字
oldVnode.elm.innerHTML = ""
for (let i = 0; i < newVnode.children.length; i++) {
let chDom = createElement(newVnode.children[i])
oldVnode.elm.appendChild(chDom)
}
}
}
}
1. diff算法中,新旧节点都有子节点的情况。
先声明四个指针,和四个指针对应的vnode。然后进行命中查找,命中代码如下,分四种情况。
-
新前和旧前
-
新后和旧后
-
新后和旧前
-
新前和旧后
命中其中一种情况就不往下查找,改变指针和对应的虚拟节点,特别要说明的是如果新后和旧前命中,需要将命中的节点移动到旧后的后面。如果新前和旧后命中,那么需要将命中的节点移动到旧前的前面。如果四种情况都没有命中的话,那么需要将当前节点去旧节点中去寻找,如果找到,移动节点。如果旧节点中不存在,那么需要创建节点并插入到旧前的前面。
function updateChildren(parentElm, oldCh, newCh) {
// 四个标记
let newStartIdx = 0
let oldStartIdx = 0
let newEndIdx = newCh.length - 1
let oldEndIdx = oldCh.length - 1
// 四个标记对应的vnode
let newStartVnode = newCh[newStartIdx]
let oldStartVnode = oldCh[oldStartIdx]
let newEndVnode = newCh[newEndIdx]
let oldEndVnode = oldCh[oldEndIdx]
let keyMap;
// console.log(newStartVnode, oldStartVnode)
// console.log(newEndVnode, oldEndVnode)
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
console.log("☆")
// while语句最开始应该略过undefined,再判断四种情况
if (newStartVnode == null || newStartVnode == undefined) {
newStartVnode = newCh[++newStartIdx]
} else if (newEndVnode == null || newEndVnode == undefined) {
newEndVnode = newCh[--newEndIdx]
} else if (oldStartVnode == null || oldStartVnode == undefined) {
oldStartVnode = oldCh[++oldStartIdx]
} else if (oldEndVnode == null || oldEndVnode == undefined) {
oldEndVnode = oldCh[--oldEndIdx]
} else if (sameVnode(newStartVnode, oldStartVnode)) {
console.log("1新前和旧前")
patchVnode(oldStartVnode, newStartVnode)
newStartVnode = newCh[++newStartIdx]
oldStartVnode = oldCh[++oldStartIdx]
} else if (sameVnode(newEndVnode, oldEndVnode)) {
console.log("2新后和旧后")
patchVnode(oldEndVnode, newEndVnode)
newEndVnode = newCh[--newEndIdx]
oldEndVnode = oldCh[--oldEndIdx]
} else if (sameVnode(newEndVnode, oldStartVnode)) {
console.log("3新后和旧前")
// 情况三命中需要移动节点到旧后的后面
parentElm.insertBefore(oldStartVnode.elm, oldEndVnode.elm.nextSibling)
patchVnode(oldStartVnode, newEndVnode)
newEndVnode = newCh[--newEndIdx]
oldStartVnode = oldCh[++oldStartIdx]
} else if (sameVnode(newStartVnode, oldEndVnode)) {
console.log("4新前和旧后")
// 情况四命中需要移动节点到旧前节点的前面
parentElm.insertBefore(oldEndVnode.elm, oldStartVnode.elm)
patchVnode(oldEndVnode, newStartVnode)
newStartVnode = newCh[++newStartIdx]
oldEndVnode = oldCh[--oldEndIdx]
} else {
// 四种情况都没有命中的情况
console.log("5四种情况都没有命中")
// 生成一个keyMap
if (!keyMap) {
keyMap = {}
for (let i = oldStartIdx; i <= oldEndIdx; i++) {
let key = oldCh[i].key
if (key) {
keyMap[key] = i
}
}
}
// console.log(keyMap)
let idxInOld = keyMap[newStartVnode.key]
if (idxInOld == undefined) {
// 如果新节点不在旧节点中,那么新增
parentElm.insertBefore(createElement(newStartVnode), oldStartVnode.elm)
} else {
// 如果新节点在旧节点中,那么移动
let moveToVnode = oldCh[idxInOld]
// 对比一下
patchVnode(moveToVnode, newStartVnode)
// 移动
parentElm.insertBefore(moveToVnode.elm, oldStartVnode.elm)
// 标记当前节点已处理
oldCh[idxInOld] = undefined
}
// 改变指针
newStartVnode = newCh[++newStartIdx]
}
}
// 循环结束还有两种情况
if (newStartIdx <= newEndIdx) {
// 新节点比旧节点多,那么就需要将新节点中未处理的节点添加到旧节点中
console.log('新节点比旧节点多')
for (let i = newStartIdx; i <= newEndIdx; i++) {
parentElm.insertBefore(createElement(newCh[i]), oldStartVnode.elm)
}
} else if (oldStartIdx <= oldEndIdx) {
// 旧节点比新节点多, 那么需要删除旧节点
console.log("旧节点比新节点多")
for (let i = oldStartIdx; i <= oldEndIdx; i++) {
if (oldCh[i]) {
parentElm.removeChild(oldCh[i].elm)
}
}
}
}