vue源码学习
文章目录
- vue源码学习
- 一、不加key或者key是索引会怎么样?
- 二、vue为什么要加key?
- 总结
一、不加key或者key是索引会怎么样?
<div id="app">
<div>
<input type="text" v-model="myName">
<button @click="add">增加</button>
</div>
<ul a='1' style="color:red">
<li v-for="item in arr"> <input type="checkbox">{{item.name}}</li>
</ul>
</div>
<script src="https://cdn.staticfile.org/vue/2.2.2/vue.min.js"></script>
<script>
const vm = new Vue({
el:'#app',
data: {
arr:[
{id:'1', name: '西瓜'},
{id:'2', name: '葡萄'},
{id:'3', name: '冰镇汽水'}
],
myName: '',
myId: 3,
},
methods: {
add() {
this.arr.unshift({id:++this.myId, name: this.myName});
this.myName= '';
}
}
})
vm.$mount('#app')
</script>
运行之后长这样:
当不加key时,增加了一个柠檬选项,发现勾选的数据变了:
不加key会出现bug,那加key,把key变成索引会怎么样?
结果是一样的,除非把key换成唯一标识。
二、vue为什么要加key?
由上文可知,不写key或把key写成索引都会有问题的。为什么呢?
渲染真实的dom时,并不是暴力覆盖原有的dom,而是比对新旧两个vnode(虚拟节点),如果不是同一个节点,删除老的,替换成新的;如果是同一个节点,就复用老节点,增加新节点的属性。
对应源码:\src\core\vdom\patch.js
判断两个节点是否相同:
export function isSameVnode(vnode1, vnode2) {
return vnode1.tag === vnode2.tag && vnode1.key === vnode2.key;
}
标签名和key相同会被判断为同一个节点。
Diff算法中,比较相同节点有一些优化:头头比较(新旧节点的起始索引)、尾尾比较、头尾比较、尾头比较:
function updateChildren(el, oldChildren, newChildren) {
// 操作列表
// vue2采用双指针的方法,比较两个节点
let oldStartIndex = 0;
let newStartIndex = 0;
let oldEndIndex = oldChildren.length - 1;
let newEndIndex = newChildren.length - 1;
let oldStartVnode = oldChildren[0];
let newStartVnode = newChildren[0];
let oldEndVnode = oldChildren[oldEndIndex];
let newEndVnode = newChildren[newEndIndex];
while(oldStartIndex <= oldEndIndex && newStartIndex <= newEndIndex) {
// 双方有一方头指针 > 尾指针则停止循环
if (isSameVnode(oldStartVnode, newStartVnode)) { // 头头
patchVnode(oldStartVnode, newStartVnode) // 如果相同节点,则递归比较子节点
oldStartVnode = oldChildren[++oldStartIndex];
newStartVnode = newChildren[++newStartIndex];
// 比较开头节点
} else if (isSameVnode(oldEndVnode, newEndVnode)) { // 尾尾
patchVnode(oldStartVnode, newStartVnode) // 如果相同节点,则递归比较子节点
oldEndVnode = oldChildren[--oldEndVnode];
newEndVnode = newChildren[--newEndVnode];
// 比较开头节点
} else if (isSameVnode(oldEndVnode, newStartVnode)) {
// 交叉比对 abdc dabc
if(isSameVnode(oldEndVnode, newStartVnode)) { // 尾头
patchVnode(oldEndVnode, newStartVnode);
el.insertBefore(oldEndVnode.el, oldStartVnode.el); // 将老的尾巴移动到新的前面
oldEndVnode = oldChildren[--oldEndIndex];
newStartVnode = newChildren[++newStartIndex]
}
} else if (isSameVnode(oldStartVnode, newEndVnode)) { // 头尾
// 交叉比对 abdc dabc
if(isSameVnode(oldEndVnode, newStartVnode)) {
patchVnode(oldStartVnode, newEndVnode);
el.insertBefore(oldStartVnode.el, oldEndVnode.el.nextSibling); // 将老的尾巴移动到新的前面
oldStartVnode = oldChildren[++oldEndIndex];
newEndVnode = newChildren[--newStartIndex];
}
}
}
if (newStartIndex <= newEndIndex) { // 多余的插入
for (let i = newStartIndex; i <= newEndIndex; i++) {
let childEl = createElm(newChildren[i]);
// 有可能向后移,有可能向前移
let anchor = newChildren[newEndIndex + 1] ? newChildren[newEndIndex + 1].el:null; // 获取下一个元素
el.insertBefore(childEl, anchor); // anchor为null被认为是appendChild
//el.appendChild(childEl);
}
}
if (oldStartIndex <= oldEndIndex) { // 老的需要删除
for (let i = oldEndIndex; i <= oldEndIndex; i++) {
let childEl = oldChildren[i].el;
el.removeChild(childEl);
}
}
}
上文是新增节点,会走以上逻辑的尾尾比较:
假设 西瓜a 葡萄b 冰镇汽水c 柠檬d
老节点:a b c
新节点:d a b c
第一次比较: a b,d a b
第二次比较: a,d a
第二次比较: ,d
新节点的头指针 >= 新节点的尾指针,说明有多余的元素,把多余元素插入。这个是有key的情况,可以把字母abcd看成key。如果是无key,会被认为是同一个节点。比如柠檬和西瓜被认为是同一个节点,复用西瓜,然后把柠檬的属性给西瓜:
同一个节点,比较属性:
export function patchProps(el,oldProps = {}, props={}) {
// 老的属性中有,新的没有 要删除老的
let oldStyles = oldProps.style || {};
let newStyles = props.style || {};
for (let key in oldStyles) { // 老的样式没有要删除
if (!newStyles[key]) {
el.style[key] = ''
}
}
for (let key in oldProps) { // 老的属性没有要删除
if (!props[key]) {
el.removeAttribute(key);
}
}
for (let key in props) { // 用新的覆盖老的
if(key === 'style') {
for (let styleName in props.style) {
el.style[styleName] = props.style[styleName]
}
} else {
el.setAttribute(key, props[key])
}
}
}
如果key是索引,那新增加的柠檬索引变为0,所以同理,老节点的西瓜和新增加的柠檬会被认为是同一个节点。
总结
key 的特殊 attribute 主要用在 Vue 的虚拟 DOM 算法,在新旧 nodes 对比时辨识 VNodes。如果不使用 key,Vue 会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法。而使用 key 时,它会基于 key 的变化重新排列元素顺序,并且会移除 key 不存在的元素。
有相同父元素的子元素必须有独特的 key。重复的 key 会造成渲染错误。