页面如下
diff1.jpg代码如下
code1.png问题:当点击HelloWorld 的改变isShow的值从false变为true
会打印HelloWorld-mounted 也就是说HelloWorld组件的mouted 执行了,也就是HelloWorld组件销毁重建了。从代码看HelloWorld组件上没有v-if 所以不是业务代码控制的是否销毁,那么还有那种情况呢。对,就是vue-diff算法对比过程中销毁重建了。从代码上看。是指在代码的收尾各加了一个div。为什么会出现HelloWorld组件销毁重建呢?
duandian1.jpg从上图中可以看出newCh 新的节点和oldCh老的节点都是长度为4的虚拟节点数组。diff 的时候会两头两尾,头尾、尾头两两对比。都不满足后会走到updateChildren 的while 里面的else 从老的节点数组中查找是否有和newStartVnode相同的节点sameVnode 所以找到了 老的里面的第二个和新的里面的第一个是相同节点。
duandian2.jpg在oldCh数组里面索引为1的也就是第二个就是 HelloWorld 组件会被销毁
再次while 时 如下图
duandian3.jpg是拿老的里面的索引为2的和新的里面索引为1的被判断为相同的。newCh中的 HelloWorld 组件会被创建 所以HelloWorld组件的mounted 再次执行
vue-diff.png
function sameVnode (a, b) {
return (
a.key === b.key && (
(
a.tag === b.tag &&
a.isComment === b.isComment &&
isDef(a.data) === isDef(b.data) &&
sameInputType(a, b)
) || (
isTrue(a.isAsyncPlaceholder) &&
a.asyncFactory === b.asyncFactory &&
isUndef(b.asyncFactory.error)
)
)
)
}
// 虚拟节点中所包含的属性
export default class VNode {
tag: string | void; // 标签名
data: VNodeData | void; // 数据
children: ?Array; // 子节点数组
text: string | void; // 真实节点所对应的文本
elm: Node | void; // 对应的真实dom
key: string | number | void; // 元素上定义的key
parent: VNode | void; // component placeholder node
isStatic: boolean; // hoisted static node // 是否是静态节点
isComment: boolean; // 是否是注释节点
isCloned: boolean; // is a cloned node?
isOnce: boolean; // is a v-once node? // 是否是v-once的节点
}
// 更新子节点
function updateChildren (parentElm, oldCh, newCh) {
// 定义两个指针分别指向两个子节点数组的开始
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
// 当新老节点都没有对比完一直循环
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)
// 移动两个指针
oldStartVnode = oldCh[++oldStartIdx]
newStartVnode = newCh[++newStartIdx]
}
// 同上
else if (sameVnode(oldEndVnode, newEndVnode)) {
patchVnode(oldEndVnode, newEndVnode)
oldEndVnode = oldCh[--oldEndIdx]
newEndVnode = newCh[--newEndIdx]
}
// 首尾比较
else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
patchVnode(oldStartVnode, newEndVnode)
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)
canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
oldEndVnode = oldCh[--oldEndIdx]
newStartVnode = newCh[++newStartIdx]
}
// 使用v-for 建议添加key 当两首两尾首尾都不值得比较,这个时候如果定义了key就会通过key查找是否值得比较
else {
if (isUndef(oldKeyToIdx))
// createKeyToOldIdx会返回一个map key就是老元素的key值是老元素的索引(oldKeyToIdx)
oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
// findIdxInOld方法 (在老的里面查找出值得比较的元素)
idxInOld = isDef(newStartVnode.key)
? oldKeyToIdx[newStartVnode.key]
: findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
// idxInOld (老子节点数组中值得比较的节点的索引位置)
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)
oldCh[idxInOld] = undefined
canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
} else {
// key 相同但是元素不同,按新元素处理
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)
} else if (newStartIdx > newEndIdx) {
//老的子节点数组长度长 删除多余的
removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
}
}