原文地址:道招網的用個陣列來理解vue的diff演算法(一)
Vue使用的diff演算法,我相信用vue的估計都聽過,並且看到原始碼的也不在少數。 先對下面的程式碼做下說明:
- 由於這裡用的是陣列內元素的有移動來模擬dom元素的移動,所以簡單的
insertBefore
之類的就不能用了,這裡是靠刪除+新增來模擬的移動。 - 這裡沒有在陣列裡面用純數字,一是因為這樣不顯示,真實的vue裡面肯定不會是基礎資料型別,另一個是因為基礎資料也不好比較,這麼判斷這個
2
是跟第一個2
相同還是跟第二個2
相同呢。 patchVnode
裡面的打補丁功能暫時沒寫,下一篇文章會講。- 保留了
console.log
資訊,方便理解
var a1 = {
key: 1,
tag: "li",
elm: "aaa1"
};
var a2 = {
key: 2,
tag: "li",
elm: "aaa2"
};
var a3 = {
key: 3,
tag: "li",
elm: "aaa3"
};
var a4 = {
key: 4,
tag: "li",
elm: "aaa4"
};
var a5 = {
key: 5,
tag: "li",
elm: "aaa5"
};
var a6 = {
key: 6,
tag: "li",
elm: "aaa6"
};
var nodeOps = {
findIndex: function(target, parent) {
return parent.findIndex(item => item === target);
},
delete: function(target, parent) {
const index = nodeOps.findIndex(target, parent);
console.log("刪除", index > -1);
if (index > -1) {
parent.splice(index, 1);
}
},
insertBefore: function(parent, target, sibling) {
nodeOps.delete(target, parent);
if (sibling) {
const index = nodeOps.findIndex(sibling, parent);
parent.splice(index, 0, target);
} else {
parent.push(target);
}
},
createElement: function(tagName, vnode) {
return document.createElement(tagName);
},
isParentNode: function(target, parent) {
return parent.includes(target);
},
appendChild: function(target, parent) {
parent.push(target);
},
sibling: function(target, parent) {
const index = nodeOps.findIndex(target, parent);
return parent[index + 1];
}
};
function isUndef(v) {
return v === undefined || v === null;
}
function isDef(v) {
return v !== undefined && v !== null;
}
function isTrue(v) {
return v === true;
}
function isFalse(v) {
return v === false;
}
function insert(parent, elm, ref$$1) {
if (isDef(parent)) {
if (isDef(ref$$1)) {
if (nodeOps.isParentNode(ref$$1, parent)) {
nodeOps.insertBefore(parent, elm, ref$$1);
}
} else {
nodeOps.appendChild(parent, elm);
}
}
}
function createElm(
vnode,
insertedVnodeQueue,
parentElm,
refElm,
nested,
ownerArray,
index
) {
console.log("新建node");
if (isDef(vnode) && isDef(ownerArray)) {
// This vnode was used in a previous render!
// now it's used as a new node, overwriting its elm would cause
// potential patch errors down the road when it's used as an insertion
// reference node. Instead, we clone the node on-demand before creating
// associated DOM element for it.
vnode = ownerArray[index] = cloneVNode(vnode);
}
var children = vnode.children;
{
insert(parentElm, vnode, refElm);
}
}
function cloneVNode(vnode) {
const cloned = JSON.parse(JSON.stringify(vnode));
cloned.isCloned = true;
return cloned;
}
function addVnodes(
parentElm,
refElm,
vnodes,
startIdx,
endIdx,
insertedVnodeQueue
) {
for (; startIdx <= endIdx; ++startIdx) {
createElm(
vnodes[startIdx],
insertedVnodeQueue,
parentElm,
refElm,
false,
vnodes,
startIdx
);
}
}
function removeVnodes(parentElm, vnodes, startIdx, endIdx) {
for (; startIdx <= endIdx; ++startIdx) {
var ch = vnodes[startIdx];
if (isDef(ch)) {
if (isDef(ch.tag)) {
console.log("刪除相關操作", ch);
nodeOps.delete(ch, parentElm);
} else {
// Text node
removeNode(ch.elm);
}
}
}
}
function patchVnode(
oldVnode,
vnode,
insertedVnodeQueue,
ownerArray,
index,
removeOnly
) {
if (oldVnode === vnode) {
return;
}
if (isDef(vnode) && isDef(ownerArray)) {
console.log("clone ", vnode);
// clone reused vnode
vnode = ownerArray[index] = cloneVNode(vnode);
}
var elm = (vnode.elm = oldVnode.elm);
var oldCh = oldVnode.children;
var ch = vnode.children;
if (isDef(oldCh) && isDef(ch)) {
if (oldCh !== ch) {
updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly);
}
}
}
function isSameNode(oldVnode, newVnode) {
return isSameContentNode(oldVnode, newVnode);
}
function getVnodeContent(vnode) {
const content = Object.assign({}, vnode);
delete content.key;
return content;
}
// 簡單的用字串來比較。
function isSameContentNode(oldVnode, newVnode) {
return (
JSON.stringify(getVnodeContent(oldVnode)) ===
JSON.stringify(getVnodeContent(newVnode))
);
}
function findIdxInOld(node, oldCh, start, end) {
for (var i = start; i < end; i++) {
var c = oldCh[i];
if (isDef(c) && sameVnode(node, c)) {
return i;
}
}
}
function updateChildren(
parentElm,
oldCh,
newCh,
insertedVnodeQueue,
removeOnly
) {
function printIndex() {
console.log(
"old=",
oldStartIdx,
oldEndIdx,
"; new=",
newStartIdx,
newEndIdx
);
}
let oldStartIdx = 0,
oldEndIdx = oldCh.length - 1,
newStartIdx = 0,
newEndIdx = newCh.length - 1,
oldKeyToIdx,
vnodeToMove;
let oldStartVnode = oldCh[oldStartIdx],
oldEndVnode = oldCh[oldEndIdx],
newStartVnode = newCh[newStartIdx],
newEndVnode = newCh[newEndIdx];
while (newStartIdx <= newEndIdx && oldStartIdx <= oldEndIdx) {
printIndex();
if (isUndef(oldStartVnode)) {
oldStartVnode = oldCh[++oldStartIdx]; // Vnode has been moved left
} else if (isUndef(oldEndVnode)) {
oldEndVnode = oldCh[--oldEndIdx];
} else if (oldStartVnode === newStartVnode) {
console.log("首首相同,平移");
patchVnode(
oldStartVnode,
newStartVnode,
insertedVnodeQueue,
newCh,
newStartIdx
);
oldStartVnode = oldCh[++oldStartIdx];
newStartVnode = newCh[++newStartIdx];
} else if (oldEndVnode === newEndVnode) {
patchVnode(
oldEndVnode,
newEndVnode,
insertedVnodeQueue,
newCh,
newEndIdx
);
console.log("尾尾相同,平移");
oldEndVnode = oldCh[--oldEndIdx];
newEndVnode = newCh[--newEndIdx];
} else if (oldStartVnode == newEndVnode) {
console.log("首尾相同,右移動");
patchVnode(
oldStartVnode,
newEndVnode,
insertedVnodeQueue,
newCh,
newEndIdx
);
nodeOps.insertBefore(
parentElm,
oldStartVnode,
nodeOps.sibling(oldEndVnode, parentElm)
);
oldStartVnode = oldCh[++oldStartIdx];
newEndVnode = newCh[--newEndIdx];
} else if (oldEndVnode == newStartVnode) {
console.log("尾獸相同,左移動");
patchVnode(
oldEndVnode,
oldStartVnode,
insertedVnodeQueue,
newCh,
newStartIdx
);
nodeOps.insertBefore(parentElm, oldEndVnode, oldStartVnode);
oldEndVnode = oldCh[--oldEndIdx];
newStartVnode = newCh[++newStartIdx];
} else {
console.log("無法簡單移動");
if (!oldKeyToIdx) {
oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx);
}
var idxInOld = isDef(newStartVnode.key)
? oldKeyToIdx[newStartVnode.key]
: findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx);
if (isUndef(idxInOld)) {
console.log("無key");
createElm(
newStartVnode,
insertedVnodeQueue,
parentElm,
oldStartVnode,
false,
newCh,
newStartIdx
);
} else {
vnodeToMove = oldCh[idxInOld];
if (isSameContentNode(vnodeToMove, newStartVnode)) {
console.log("有key:可以直接移動");
patchVnode(
vnodeToMove,
newStartVnode,
insertedVnodeQueue,
newCh,
newStartIdx
);
oldCh[idxInOld] = undefined;
nodeOps.insertBefore(
parentElm,
cloneVNode(vnodeToMove),
oldStartVnode
);
} else {
console.log("有key:但是沒用");
createElm(
newStartVnode,
insertedVnodeQueue,
parentElm,
oldStartVnode,
false,
newCh,
newStartIdx
);
}
}
newStartVnode = newCh[++newStartIdx];
}
}
printIndex();
console.log("while完畢");
if (oldStartIdx > oldEndIdx) {
console.log("需要新增");
refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm;
addVnodes(
parentElm,
refElm,
newCh,
newStartIdx,
newEndIdx,
insertedVnodeQueue
);
} else if (newStartIdx > newEndIdx) {
console.log("需要刪除");
removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx);
}
}
function generateElm(children) {
var elm = document.createElement("ul");
children.forEach(child => {
var childElm = document.createElement(child.tag);
childElm.append(document.createTextNode(child.text));
elm.append(childElm);
});
return elm;
}
function createKeyToOldIdx(children, beginIdx, endIdx) {
var i, key;
var map = {};
for (i = beginIdx; i <= endIdx; ++i) {
key = children[i].key;
if (isDef(key)) {
map[key] = i;
}
}
return map;
}
var oldVnode = {
tag: "ul",
elm: [a1, a2, a3, a4],
children: [a1, a2, a3, a4]
};
var newVnode = {
tag: "ul",
elm: [a3, a2, a4, a1],
children: [a3, a2, a4, a1]
};
// var newVnode = {
// tag: "ul",
// elm: [a4, a3, a1],
// children: [a4, a3, a1]
// };
// var newVnode = {
// tag: "ul",
// children: [a5, a4, a1, a1, a5, a2, a2]
//};
patchVnode(oldVnode, newVnode);
console.log(newVnode);
複製程式碼
現在成功利用diff
演算法根據oldVnode
的elm
和children
得到newVnode