共计 8780 个字符,预计需要花费 22 分钟才能阅读完成。
前言
1. 什么是 diff
diff
算法是一种通过同层的树节点进行比拟的高效算法
2.diff 具备哪些劣势
比拟只会在同层级进行, 不会跨层级比拟。在 diff
比拟的过程中,循环从两边向两头比拟
diff 流程图
patch
github: vuejs/vue@2.6.14 patch
/**
* 是否是 非空
*/
function isUndef(v: any) {return v === undefined || v === null;}
/**
* 是否不是 非空
*/
function isDef(v: any) {return v !== undefined && v !== null;}
/**
*
* @param oldVnode
* @param vnode
* @param hydrating
* @param removeOnly
*/
function patch(oldVnode, vnode, hydrating, removeOnly) {
// 如果 新 vnode 不存在
if (isUndef(vnode)) {
// 如果 老 vnode 存在,if (isDef(oldVnode)) invokeDestroyHook(oldVnode);
return;
}
let isInitialPatch = false;
const insertedVnodeQueue = [];
// 老 vnode 不存在(首次渲染)if (isUndef(oldVnode)) {
isInitialPatch = true;
createElm(vnode, insertedVnodeQueue);
}
// 更新节点阶段
else {const isRealElement = isDef(oldVnode.nodeType);
// 不是实在节点,并且 新、老节点是同一节点
if (!isRealElement && sameVnode(oldVnode, vnode)) {
// 进行更新节点
patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly);
} else {if (isRealElement) {
// 是否是 ssr 渲染
if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {oldVnode.removeAttribute(SSR_ATTR);
hydrating = true;
}
// ssr 渲染逻辑
if (isTrue(hydrating)) {
// 混合
if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {invokeInsertHook(vnode, insertedVnodeQueue, true);
return oldVnode;
} else if (process.env.NODE_ENV !== 'production') {
warn(
'The client-side rendered virtual DOM tree is not matching' +
'server-rendered content. This is likely caused by incorrect' +
'HTML markup, for example nesting block-level elements inside' +
'<p>, or missing <tbody>. Bailing hydration and performing' +
'full client-side render.'
);
}
}
// 不是 ssr 渲染或混合失败,则创立一个空节点
oldVnode = emptyNodeAt(oldVnode);
}
// replacing existing element
const oldElm = oldVnode.elm;
const parentElm = nodeOps.parentNode(oldElm);
// create new node
createElm(
vnode,
insertedVnodeQueue,
// extremely rare edge case: do not insert if old element is in a
// leaving transition. Only happens when combining transition +
// keep-alive + HOCs. (#4590)
oldElm._leaveCb ? null : parentElm,
nodeOps.nextSibling(oldElm)
);
// update parent placeholder node element, recursively
if (isDef(vnode.parent)) {
let ancestor = vnode.parent;
const patchable = isPatchable(vnode);
// 遍历 父节点
while (ancestor) {
// 卸载老节点的所有组件
for (let i = 0; i < cbs.destroy.length; ++i) {cbs.destroy[i](ancestor);
}
// 替换节点
ancestor.elm = vnode.elm;
if (patchable) {for (let i = 0; i < cbs.create.length; ++i) {cbs.create[i](emptyNode, ancestor);
}
// #6513
// invoke insert hooks that may have been merged by create hooks.
// e.g. for directives that uses the "inserted" hook.
const insert = ancestor.data.hook.insert;
if (insert.merged) {
// start at index 1 to avoid re-invoking component mounted hook
for (let i = 1; i < insert.fns.length; i++) {insert.fns[i]();}
}
} else {registerRef(ancestor);
}
// 更新父节点
ancestor = ancestor.parent;
}
}
// 如果老节点存在,则删除
if (isDef(parentElm)) {removeVnodes([oldVnode], 0, 0);
}
// 否则卸载 老节点
else if (isDef(oldVnode.tag)) {invokeDestroyHook(oldVnode);
}
}
}
// 注册节点
invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);
return vnode.elm;
}
sameVnode
判断是否是同一个节点
github: vuejs/vue@2.6.14 sameVnode
/**
* 比拟节点是否雷同
*/
function sameVnode(a, b) {
return (
// 比拟 key 是否雷同
a.key === b.key &&
// 比拟是不是都是异步组件
a.asyncFactory === b.asyncFactory && // 比拟标签名称是否雷同
((a.tag === b.tag &&
// 解析代码后,比拟是否都是被正文后的节点
a.isComment === b.isComment &&
// 比拟 data 数据援用是否雷同
isDef(a.data) === isDef(b.data) &&
// 比拟 input 标签的 data、attrs、type 是否雷同
sameInputType(a, b)) ||
//
(isTrue(a.isAsyncPlaceholder) && isUndef(b.asyncFactory.error)))
);
}
sameVnode
函数应用到的工具类
/**
* 判断 a 和 b 若 input,则是否雷同
*/
function sameInputType(a, b) {
// 不比拟非 input 标签
if (a.tag !== 'input') return true;
let i;
// 判断 data、attrs、type 属性是否非空
const typeA = isDef((i = a.data)) && isDef((i = i.attrs)) && i.type;
const typeB = isDef((i = b.data)) && isDef((i = i.attrs)) && i.type;
// 判断 a 和 b 都不是非空,并且判断 input type 属性是否正确
return typeA === typeB || (isTextInputType(typeA) && isTextInputType(typeB));
}
/**
* 宰割 str 字符串,通过闭包判断 key 字段是否命中内容
*/
function makeMap(str: string, expectsLowerCase?: boolean): (key: string) => true | void {const map = Object.create(null);
const list: Array<string> = str.split(',');
for (let i = 0; i < list.length; i++) {map[list[i]] = true;
}
return expectsLowerCase ? (val) => map[val.toLowerCase()] : (val) => map[val];
}
/**
* input 属性为 输出类型的 type 值
*/
const isTextInputType = makeMap('text,number,password,search,email,tel,url');
/**
* 是否为 true
*/
function isTrue(v: any): boolean %checks {return v === true;}
patchVnode
github: vuejs/vue@2.6.14 patchVnode
/**
* 给 vnode 打补丁
* @param oldVnode 老虚构 dom 节点
* @param vnode 新虚构 dom 节点
* @param insertedVnodeQueue
* @param ownerArray
* @param index
* @param removeOnly
*/
function patchVnode(oldVnode, vnode, insertedVnodeQueue, ownerArray, index, removeOnly) {
// 若新节点和老节点援用雷同,则跳过
if (oldVnode === vnode) {return;}
//
if (isDef(vnode.elm) && isDef(ownerArray)) {
// clone reused vnode
vnode = ownerArray[index] = cloneVNode(vnode);
}
const elm = (vnode.elm = oldVnode.elm);
if (isTrue(oldVnode.isAsyncPlaceholder)) {if (isDef(vnode.asyncFactory.resolved)) {hydrate(oldVnode.elm, vnode, insertedVnodeQueue);
} else {vnode.isAsyncPlaceholder = true;}
return;
}
// 新、老节点都是动态节点,且 key 都雷同,并新的 vnode 是克隆节点或绑定了 v-once, 则赋值后完结
if (isTrue(vnode.isStatic) &&
isTrue(oldVnode.isStatic) &&
vnode.key === oldVnode.key &&
(isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
) {
vnode.componentInstance = oldVnode.componentInstance;
return;
}
// hook
let i;
const data = vnode.data;
if (isDef(data) && isDef((i = data.hook)) && isDef((i = i.prepatch))) {i(oldVnode, vnode);
}
const oldCh = oldVnode.children;
const ch = vnode.children;
if (isDef(data) && isPatchable(vnode)) {for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode);
if (isDef((i = data.hook)) && isDef((i = i.update))) i(oldVnode, vnode);
}
// 如果新 vnode 没有 文本内容,则代表可能有子节点
if (isUndef(vnode.text)) {
// 如果新、老节点都有子节点
if (isDef(oldCh) && isDef(ch)) {
// 如果子节点不雷同,则更新节点
if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly);
}
// 新 vnode 的子节点非空
else if (isDef(ch)) {
// 开发环境,查看新节点的子节点是否存在反复 key,如有则 console 抛出正告提醒
if (process.env.NODE_ENV !== 'production') {checkDuplicateKeys(ch);
}
//
if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '');
addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue);
}
// 如果新节点没有子节点,但老节点有子节点,则删除老节点的子节点
else if (isDef(oldCh)) {removeVnodes(oldCh, 0, oldCh.length - 1);
}
// 如果老节点的本人就是 文本内容,则清空
else if (isDef(oldVnode.text)) {nodeOps.setTextContent(elm, '');
}
}
// 如果新老节点的 文本内容不雷同,则更新 新节点 文本内容
else if (oldVnode.text !== vnode.text) {nodeOps.setTextContent(elm, vnode.text);
}
if (isDef(data)) {if (isDef((i = data.hook)) && isDef((i = i.postpatch))) i(oldVnode, vnode);
}
}
updateChildren
github: vuejs/vue@2.6.14 updateChildren
/**
* @param parentElm
* @param oldCh
* @param newCh
* @param insertedVnodeQueue
* @param removeOnly
*/
function updateChildren(parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
let oldStartIdx = 0;
let newStartIdx = 0;
let oldEndIdx = oldCh.length - 1;
let oldStartVnode = oldCh[0];
let oldEndVnode = oldCh[oldEndIdx];
let newEndIdx = newCh.length - 1;
let newStartVnode = newCh[0];
let newEndVnode = newCh[newEndIdx];
let oldKeyToIdx, idxInOld, vnodeToMove, refElm;
// removeOnly is a special flag used only by <transition-group>
// to ensure removed elements stay in correct relative positions
// during leaving transitions
const canMove = !removeOnly;
if (process.env.NODE_ENV !== 'production') {checkDuplicateKeys(newCh);
}
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);
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(oldCh, oldStartIdx, oldEndIdx);
}
}
正文完