3. vue中key的作用和工作的机制都是什么?
创建demo
<button @click="set">set</button>
<p v-for="child in children">{{child}}</p><!-- 不使用key -->
<p v-for="child in children" :key="child">{{child}}</p><!-- 使用key -->
----------------------------------------------------------------------
js data声明
children: ['a', 'b', 'c', 'd', 'e']
set () { this.children.splice(3, 0, 'f') }
// set 后得到数组['a', 'b', 'c', 'f', 'd', 'e']
上述代码中,在数组children 第四项 d 前插入一项 f, 执行 加key 与 不加key 后比较区别
源码位置
url src\core\vdom\patch.js => updateChildren => sameVnode
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]
}
}
不使用key 代码执行如下
// 第1次循环patch (因为 a.key = a.key 并无渲染执行 不做处理)
oldList: a b c d e
newList: a b c f d e
// 第2次循环patch (同1)
oldList: b c d e
newList: b c f d e
// 第3次循环patch (同1)
oldList: c d e
newList: c f d e
// 第4次循环patch (虽然 d.key = f.key 但 内容发生改变 做重新渲染)
oldList: d e
newList: f d e
// 第5次循环patch (同4)
oldList: e
newList: d e
// 第6次循环patch (此时新数组多出一项,做创建dom操作)
oldList:
newList: e
结论:由此可见,当不设置key时,会按照自上而下执行,遇到不同的位置,会做重新渲染操作。删除同理,先会重新渲染,然后销毁dom,极其浪费性能。
使用key 代码执行如下
// 第1次循环patch (因为 a.key = a.key 并无渲染执行 不做处理)
oldList: a b c d e
newList: a b c f d e
// 第2次循环patch (同1)
oldList: b c d e
newList: b c f d e
// 第3次循环patch (同1)
oldList: c d e
newList: c f d e
// 第4次循环patch (此时 d.key !== f.key 会从末项比较)
oldList: d e
newList: f d e
// 第5次循环patch (同4)
oldList: d
newList: f d
// 第6次循环patch (old全部处理结束,new中剩下的f, 创建f并插入在d前面)
oldList:
newList: f
结论:设置key后,会先从首项对比,如遇到不同项,会从末项对比,避免频繁更新不同元素,减少dom操作。nice
总结
- key的作用主要是为了高效的更新虚拟DOM,原理是vue在patch 过程中通过key 可以精准判断两个节点是否相同,从而避免频繁更新不同元素,使整个patch过程更高效,减少DOM操作量,提高性能。
- 如果不设置key,会导致不同的bug,例如使用相同标签名的元素过渡切换时,会用到key 用于vue做区分,不设置会导致vue只会替换其内部属性不会触发过渡效果