对应源码的位置src\core\vdom\patch.js-updateChildren()
1.测试的案例代码
{{item}}
2.案例图解
五次更新,一次追加
而加了key的执行过程是这样的
尝试更新5次(但是并没有做操作),1次追加。
这样的话,严谨来说不加key更新了三次,追加了一次,用了key只追加了一次。
3.源码分析(执行测试代码进行调试)
头尾比较4中情况 :新节点从头开始和旧节点从头开始,新头和旧尾,旧头和新尾,旧头和旧尾
3.1 源码
//循环条件,开始索引不能大于结束索引
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
//头尾指针调整
if (isUndef(oldStartVnode)) {
oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
} else if (isUndef(oldEndVnode)) {
oldEndVnode = oldCh[--oldEndIdx]
//后面是头尾比较4中情况
} else if (sameVnode(oldStartVnode, newStartVnode)) {
patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
...
} else if (sameVnode(oldEndVnode, newEndVnode)) {
//两个开头相同
patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
//索引向后移动一位
oldStartVnode = oldCh[++oldStartIdx]
newStartVnode = newCh[++newStartIdx]
} else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
...
} else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
...
} else {
...
}
//整理工作:必定有数组还有剩下的元素未处理
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(parentElm, oldCh, oldStartIdx, oldEndIdx)
}
3.2 不加key
如果不加key,每一次都会判断为相同节点,那么就会进入patchVnode方法,强行进行更新。
判断相同节点的方法是sameVnode
function sameVnode (a, b) {
return (
a.key === b.key && (
(
a.tag === b.tag &&//标签名
a.isComment === b.isComment &&//是否为注释
isDef(a.data) === isDef(b.data) &&//data有没有发生变化
sameInputType(a, b)//是不是input类型
) || (
isTrue(a.isAsyncPlaceholder) &&
a.asyncFactory === b.asyncFactory &&
isUndef(b.asyncFactory.error)
)
)
)
}
3.3 加了key
加key的时候,如果新旧节点不是相同节点就不会进入patchVnode方法
4.总结
结论
-
- key的作用主要是为了高效的更新虚拟DOM,其原理是vue在patch过程中通过key可以精准判断两个节点是否是同一个,从而避免频繁更新不同元素,使得整个patch过程更加高效,减少DOM操作量,提高性能。
- 2.另外,若不设置key还可能在列表更新时引发一些隐蔽的bug
-
- vue中在使用相同标签名元素的过渡切换时,也会使用到key属性,其目的也是为了让vue可以区分它们,否则vue只会替换其内部属性而不会触发过渡效果。
- 4.带上原理解释一遍:
源码在patch的过程中,会执行patchVnode,patchVnode过程中会执行updateChildren这个方法,他会更新所有的两个新旧的子元素,那么在这个过程中,通过key就可以精准的判断,当前在循环的这两个节点是不是一个节点。
如果没有加key的话,永远会认为是相同的节点,所以能做的操作只有强硬的去更新,这样的话在这个过程,就没有办法避免频繁的更新过程,需要额外做很多dom操作。不加key的性能就会很差
如果加上key,就可以通过一系列内部优化算法,比如说猜测收尾结构的相似性,由于大部分情况下,元素不会发生大量的位置变化,所以会很高效的结束循环,减少大量的更新过程。