源码中找答案:corn/vdom/patch.js (updateChildren方法)
demo:
const app = new vue({
el:'#demo',
data: {item:['a','b','c','d',]},
mounted(){
setTimeout(() => {
this.item.splice(2,0,'E')
})
}
})
执行以上代码,正常返回的结果应该是:a,b,e,c,d ,e插入到b和c中间;但是在不适用key的情况回发生什么呢?
由于没有key,程序识别不出来到底要更新谁。所以就只能做一个操作,那就是见着谁就更新谁,这样就会形成一种错误,不改更新的被更新了,需要更新的却没有准确的被更新。
那么再来看下有key的情况
// 第一次循环 patch A
A B C D
A B E C D
// 第二次循环 patch B
B C D
B E C D
// 第三次循环 根据首尾判断策略 这次patch D
C D
E C D
// 第四次循环 patch C
C
E C
// 最后一次新数组只剩下E了,老数组中已经全部patch结束,创建F,插入到C前面
我们现在来看下源码的解释
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)) { // 老的开始节点与新的结束节点作比较 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)) { // 老的结束节点与新的开始节点作比较 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) } }
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) ) ) )}
假如没有使用key情况,首先我们去daptch A的时候会走进else if (sameVnode(oldStartVnode, newStartVnode)) { // 老的开头节点与新的开头节点比较
的判断里面,然后执行sameVnode方法,来看下sameVnode方法里面的逻辑;
首先判断新老数据的key是否相同,因为在没有使用key的情况下a.key与b.key都是undefined所以第一层判断为true;然后再来看下tag标签是否相同,毋庸置疑,标签也是相同的。也都不是注释,data也没有发生变化,并且也不是input标签,那么就会认为这两个是相同的节点,就开始打补丁。一次类推,每一步都会走的这一层,就会被强制更新
当有key的情况呢,当patch完B的时候,就会走进else if (sameVnode(oldEndVnode, newEndVnode)) { // 老的末尾节点与新的末尾节点作比较 这层判断,然后以此类推。走进最后一个循环结束的 时候,就已经剩下E了,就会走进这个判断里
if (oldStartIdx > oldEndIdx) {
// 将新的索引值加1,也就是要找到要追加哪个元素的前面还是后面
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)
}
这个时候oldStartIdx是2,oldEndIdx是1,所以进入判断。执行addVnodes方法,进行节点的追加
结论:key的作用主要是高效的更新虚拟dom,从源码层面解释,执行的是daptch.js里面的updateChildren方法,进行节点之间的patch操作,根据判断新老节点使用someVnode方法,首先判断key是否相同,再判断标签,如果没有key的情况,就会认为是相同的节点进行强制更新,如果有key的情况,会根据key是否相同进行更新
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!