在Vue官网中强烈推荐在使用v-for时要加key属性,这篇文章看看加属性key和不加属性key的区别。我们模拟一下Vue中对这部分的处理流程。先创建3个div元素,内容为
xiaozhang
xiaowang
xiaoliu
然后在第一个div元素之前再添加一个新div元素xiaoli,渲染后的内容如下:
xiaoli
xiaozhang
xiaowang
xiaoliu
我们用一个对象数组来模拟这一过程,对象数组如下:
//带key数据
var oldList = [{ key : 10, name : "xiaozhang", tag : "div"},
{ key : 11, name : "xiaowang", tag : "div"},
{ key : 12, name : "xiaoliu", tag : "div"}]
var newList = [{ key : 9, name : "xiaoli", tag : "div"},
{ key : 10, name : "xiaozhang", tag : "div"},
{ key : 11, name : "xiaowang", tag : "div"},
{ key : 12, name : "xiaoliu", tag : "div"}]
//不带key数据
var oldList_ = [{ name : "xiaozhang", tag : "div"},
{ name : "xiaowang", tag : "div"},
{ name : "xiaoliu", tag : "div"}]
var newList_ = [{ name : "xiaoli", tag : "div"},
{ name : "xiaozhang", tag : "div"},
{ name : "xiaowang", tag : "div"},
{ name : "xiaoliu", tag : "div"}]
带key的数据里面对象有key属性,模拟使用v-for时带key属性(:key="item.key")。
首先我们用oldList创建3个div元素,并插入到父节点中,然后用newList来更新这3个div元素,并插入新元素"xiaoli"。代码如下:
<html>
<head>
<style type="text/css">
</style>
</head>
<body>
<div id="app">
</div>
<script>
//带key数据
var oldList = [{ key : 10, name : "xiaozhang", tag : "div"},
{ key : 11, name : "xiaowang", tag : "div"},
{ key : 12, name : "xiaoliu", tag : "div"}]
var newList = [{ key : 9, name : "xiaoli", tag : "div"},
{ key : 10, name : "xiaozhang", tag : "div"},
{ key : 11, name : "xiaowang", tag : "div"},
{ key : 12, name : "xiaoliu", tag : "div"}]
//不带key数据
var oldList_ = [{ name : "xiaozhang", tag : "div"},
{ name : "xiaowang", tag : "div"},
{ name : "xiaoliu", tag : "div"}]
var newList_ = [{ name : "xiaoli", tag : "div"},
{ name : "xiaozhang", tag : "div"},
{ name : "xiaowang", tag : "div"},
{ name : "xiaoliu", tag : "div"}]
//首先创建3个div元素
function createDom(parentElm, list){
var dom3 = document.createElement(list[2].tag);
dom3.textContent = list[2].name;
parentElm.appendChild(dom3);
var dom2 = document.createElement(list[1].tag);
dom2.textContent = list[1].name;
parentElm.insertBefore(dom2, dom3);
var dom1 = document.createElement(list[0].tag);
dom1.textContent = list[0].name;
parentElm.insertBefore(dom1, dom2);
}
// createDom(document.getElementById("app"), oldList_); //不带key
createDom(document.getElementById("app"), oldList); //带key
function isSameVNode(oldNode, newNode){
//根据key判断新旧两个节点是否相同
return (oldNode.key === newNode.key) && (oldNode.tag === newNode.tag);
}
//更新节点
function updateDom(parentElm, oldList, newList){
var oldStartIdx = 0, newStartIdx = 0;
var oldEndIdx = oldList.length - 1, newEndIdx = newList.length - 1;
var oldStartVNode = oldList[0], newStartVNode = newList[0];
var oldEndVNode = oldList[oldEndIdx], newEndVNode = newList[newEndIdx];
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx){
if(isSameVNode(oldStartVNode, newStartVNode)){
//从新旧节点列表的开始部分开始遍历,不带key的节点走这个分支,因为不带key,所以key都是undefined
if(oldStartVNode.name !== newStartVNode.name){
console.log("update dom"); //新旧节点内容不同,更新dom中的内容
parentElm.children[newStartIdx].textContent = newStartVNode.name;
}
oldStartVNode = oldList[++oldStartIdx];
newStartVNode = newList[++newStartIdx];
}else if(isSameVNode(oldEndVNode, newEndVNode)){
//从新旧节点列表的结尾部分开始遍历,带key的节点走这个分支
if(oldEndVNode.name !== newEndVNode.name){
//从结尾部分遍历时,前三个dom元素内容都相同,所以不会走这里,也就没有操作dom,
parentElm.children[newEndIdx].textContent = newEndVNode.name;
}
oldEndVNode = oldList[--oldEndIdx];
newEndVNode = newList[--newEndIdx];
}
}
if (oldStartIdx > oldEndIdx) {
//遍历完后,新节点比旧节点多,所以要增加节点
if(newList[newEndIdx + 1] !== undefined){
//带key的走这个分支,在3个子节点的前面插入一个元素"xiaoli"
var dom0 = document.createElement(newList[0].tag);
dom0.textContent = newList[0].name;
parentElm.insertBefore(dom0, parentElm.children[newEndIdx]);
}else{
//不带key的走这个分支,在3个子节点最后插入一个元素“xiaoliu"
var dom3 = document.createElement(newList[newEndIdx].tag);
dom3.textContent = newList[newEndIdx].name;
parentElm.appendChild(dom3);
}
}
}
setTimeout(() => {
//更新dom
// updateDom(document.getElementById("app"), oldList_, newList_); //不带key
updateDom(document.getElementById("app"), oldList, newList); //带key
}, 2000);
</script>
</body>
</html>
在代码中我们进行了详细的注释,如哪里有不明白的地方可以单步调试一下。
当带key时,从对象数组的最后一个元素开始遍历,由于元素内容textContent相同所以不用操作dom,复用之前的dom元素,什么也不做,遍历下一个元素。最后在最前面插入新添加的元素即可。如下图所示:
当不带key时,从对象数组的第一个元素开始遍历,由于旧对象数组的第一个元素(xiaozhang)和新对象数组的第一个对象元素不同(xiaoli),所以要操作dom,把textContent改为"xiaoli",新旧数组的第二个元素,和第三个元素处理方式和第一个元素相同,都需要操作dom,最后还要插入第四个元素“xiaoliu”。如下图所示: