在使用v-for进行列表渲染时,我们通常会给元素或者组件绑定一个Key属性。
那么这个key有什么用呢?官方的解释:
- key属性主要用在Vue的虚拟DOM算法,在新旧nodes对比时辨识VNodes;
- 如果不使用key,Vue会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法;
- 而使用key时,它会基于key的变化重新排列元素顺序,并且会移除/销毁key不存在的元素;
那么什么是虚拟DOM算法?什么又是VNodes?
VNodes:Virtual Node,顾名思义也就是虚拟节点。
我们在template模板里面写的元素,vue并不会根据template模板去创建真实的DOM元素,而是在变成真实DOM元素前有一个中间环节,就是创建VNode。那么VNode的本质是什么?
其实VNode就是一个JavaScript对象。见下图:
<!-- DOM节点 -->
<div class="box1">我是box</div>
vue会根据template模板的元素创建下图这样的一个个JavaScript对象,这个对象就是VNode。
// 虚拟节点的Javascript对象
const vNode = {
type: "div",
props: {
class: 'box1'
},
children: "我是box"
}
最后根据VNode再转化成真实的DOM元素。
虚拟DOM:上述只是一个dom节点所创建出来的一个VNode,如果不只是一个简单的div元素,而是由一层层的嵌套关系所构建出来的VNodes,就会形成一个VNode Tree,这样的树结构就是虚拟DOM
那么为什么需要创建出来虚拟DOM?为什么什么不直接创建真实的DOM呢?
- 方便diff算法(在比较新旧节点的时候,比较只会在同层级进行, 不会跨层级比较)
- 方便对当前代码跨平台(因为虚拟DOM只是一个JS对象)
- 可以转化成真实DOM,渲染在浏览器上
- 可以解析成移动端的节点,比如IOS端的UIButton/UIView等等
- 渲染成桌面端的控件
- VR设备
- 等等....
在没有key的情况下,渲染真实DOM的开销是很大的,当我们修改了某个数据,如果直接渲染到真实dom上会引起整个dom树的重绘和重排,而diff算法只更新我们修改的那一小块dom而不会更新整个dom,这个key就是进行diff算法的依据。有key,vue会使用 patchKeyedChildren方法,没有key,vue会使用 patchUnkeyedChildren方法
有key的diff算法:
- 从头开始进行遍历、比较,遇到相同的就继续当key不一致,所以就会break跳出循环。
- 从尾部开始遍历,遇到相同的就继续,不同的就跳出循环
- 如果旧节点遍历完毕,但是依然有新的节点,那么就新增节点
- 是如果新的节点遍历完毕,但是依然有旧的节点,那么就移除旧节点
- 如果中间存在不知道如何排列的位置序列,那么就使用key建立索引图,最大限度的使用旧节点
总结:
- key属性是DOM元素的唯一标识,当DOM发生增删改操作时,会根据key来判断是否存在相同的 key , 相同则复用 ,这样不浪费资源
- key的作用主要是为了高效的更新虛拟DOM,其原理是vue在patch(补丁)过程中通过key可以精准判断两个节点是否是同一个,从而避免频繁更新不同元素,使得整个patch过程更加高效,减少DOM操作量,提高性能。
补充:key尽量不要使用index,当我们向除了尾部之外的位置插入元素时,整个列表的index也会发生改变,这种情况下key就不是唯一的标识