1.什么叫patch?简单了解patch做了什么?
据上文了解,我们为了节省频繁操作DOM,建立了一个vnode虚拟节点,根据新旧节点的差异,我们才去操作真实的DOM。而关于比较的这个过程,我们就叫patch。
var patch = createPatchFunction();
Vue.prototype.__patch__ = patch
patch做了什么?
- 创建新增的节点(元素节点,注释节点,文本节点)
- 删除已经不存在的节点
- 修改需要更新的节点
2.patch主要流程
3.新增节点
if (isUndef(oldVnode)) { //如果oldValue为空
isInitialPatch = true;
createElm(vnode, insertedVnodeQueue, parentElm, refElm);
}
对于三种节点的区分:元素节点必定有tag,注释节点isComment必定为true,如果两者皆不满足,那么就是文本节点
if (isDef(tag)) {}
else if (isTrue(vnode.isComment)){}
else{}
4.删除节点
function removeVnodes (vnodes, startIdx, endIdx) {
for (; startIdx <= endIdx; ++startIdx) {
var ch = vnodes[startIdx];
if (isDef(ch)) {
if (isDef(ch.tag)) {
removeAndInvokeRemoveHook(ch); //移除DOM并执行remove钩子函数
invokeDestroyHook(ch); //执行 module 的 destory 钩子函数以及 vnode 的 destory 钩子函数
} else { // Text node
removeNode(ch.elm);
}
}
}
}
5.更新节点
1.如果是静态节点会直接跳过更新
2.如果新节点具有text属性且不等于旧节点的text,那么不论之前的子节点是什么,直接调用setTextContent方法将DOM节点内容改成text属性保存的文字
3.判定是否有children,如果新节点无children。那么就直接删除旧节点的childern。如果有children,我们需要对children进行更加详细的对比,包括删除,新增。移动位置等
6.关于diff的实现策略 updateChildren方法
- 新前: newChildren中所有未处理的第一个节点
- 新后: newChildren中所有未处理的最后一个节点
- 旧前: oldChildren中所有未处理的第一个节点
- 旧后: oldChildren中所有未处理的最后一个节点
比较更新步骤
新前与旧前
sameVnode(oldStartVnode, newStartVnode)
新尾与旧尾
sameVnode(oldStartVnode, newStartVnode)
新尾与旧头
sameVnode(oldStartVnode, newEndVnode)
此时如果节点相同就需要移动位置.源代码是:
nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm));
function insertBefore (parentNode, newNode, referenceNode) {
parentNode.insertBefore(newNode, referenceNode);
}
insertBefore函数,如果当referenceNode为空时会插在当前父节点末尾
新头与旧尾
sameVnode(oldEndVnode, newStartVnode) //比较
nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm) //插入
创建索引,遍历查询
createKeyToOldIdx && findIdxInOld
遍历新节点是否能在旧节点列表中能否找到,如果不能找到,直接创建,如果能找到,
比较是否是同一个节点,如果是移动到oldStartVnode节点的前面.如果不同,
直接创建插入 oldStartVnode 前面
处理剩余的节点
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(oldCh, oldStartIdx, oldEndIdx);
}
附上原代码
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)) { //判定是否存在,是因为每次匹配到一个节点时,会将这个节点设置为undefined或者false
oldStartVnode = oldCh[++oldStartIdx]; // Vnode has been moved left 会
} else if (isUndef(oldEndVnode)) {
oldEndVnode = oldCh[--oldEndIdx];
} else if (sameVnode(oldStartVnode, newStartVnode)) { //旧头新头
patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue);
oldStartVnode = oldCh[++oldStartIdx];
newStartVnode = newCh[++newStartIdx];
} else if (sameVnode(oldEndVnode, newEndVnode)) { //旧尾与新尾
patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue);
oldEndVnode = oldCh[--oldEndIdx];
newEndVnode = newCh[--newEndIdx];
} else if (sameVnode(oldStartVnode, newEndVnode)) { // 旧头与新尾
patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue);
canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm));
oldStartVnode = oldCh[++oldStartIdx];
newEndVnode = newCh[--newEndIdx];
} else if (sameVnode(oldEndVnode, newStartVnode)) { // 旧尾与新头
patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue);
canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm);
oldEndVnode = oldCh[--oldEndIdx];
newStartVnode = newCh[++newStartIdx];
} else {
if (isUndef(oldKeyToIdx)) { //创建旧节点的序列号
oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx); }
idxInOld = isDef(newStartVnode.key)
? oldKeyToIdx[newStartVnode.key]
: findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx);
if (isUndef(idxInOld)) { // 新节点无法在旧节点中找到
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx);
} else {
vnodeToMove = oldCh[idxInOld];
if (sameVnode(vnodeToMove, newStartVnode)) {
patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue);
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(oldCh, oldStartIdx, oldEndIdx);
}
}