使用v-for进行列表渲染时,vue默认使用就地复用原则。在列表数据变化时,会根据key的值去判断节点是否修改,如果修改,则重新渲染此项。否则直接复用之前的元素;我们在日常开发的时候,经常会使用下标:index来作为列表节点的key,但是其实这是一种不被推荐的做法,先上图,大家可以感受一下。
下面是一个小demo,使用index作key和使用唯一标识id做key的一个性能对比:
下面是在列表数据中间位置插入一条新的数据的方法
index:


id:


通过谷歌的performance工具可以看出来,在使用id作为key的时候,程序性能得到明显提升,然而这只是一个小小的demo,在日常开发过程中,我们需要尽量避免使用index作为列表的key。
下面来看两种日常开发中比较常见的场景:
在列表最尾端push一条数据:

可以看到,在push时,向列表尾部插入一条数据,不会改变列表其他项的顺序和排列,index不会改变,这时是没有问题的
在列表中间部位插入一条数据:

但是在向中间插入数据时,插入位置及以后各项index发生改变,这时使用index作为key就会存在问题
这时其实后三项并没有发生数据变化,节点也无任何修改,但是由于key发生了变化,就需要重新渲染,这无疑增加了无用的性能开支。
vue内部使用vertual DOM,virtual DOM是将真实DOM的数据抽离出来,以对象形式模拟树形结构。比如:
<div> <p>virtual DOM</p></div> |
对应的vertual DOM:
var Vnode = { tag: 'div', children: [ { tag: 'p', text: 'virtual DOM' } ]}; |
而vue在计算节点变化时采用的是diff算法,比较vNode和oldVnode的差异,以进行不同的处理策略。
在采用diff算法进行比较时,只会在同级内进行比较。如下图所示:

算法内会去判断新老节点是否值得比较,如果不值得比较则直接采取替换策略,下面是diff算法进行比较的过程:
function sameVnode (a, b) { return ( a.key === b.key && // key值 a.tag === b.tag && // 标签名 a.isComment === b.isComment && // 是否为注释节点 // 是否都定义了data,data包含一些具体信息,例如onclick , style isDef(a.data) === isDef(b.data) && sameInputType(a, b) // 当标签是<input>的时候,type必须相同 )} |
可以看到,在比较的第一行就出现了key值得比较,回到咱们一开始所说的,如果使用index作为key值得话,列表数据改变引起index发生变化的话,发生变化的数据将都被判定为不值得比较,则会重新渲染,引起重复性能开支。
所以推荐使用唯一标识作为渲染列表的key值。key的作用主要是为了高效的更新虚拟DOM。另外vue中在使用相同标签名元素的过渡切换时,也会使用到key属性,其目的也是为了让vue可以区分它们,否则vue只会替换其内部属性而不会触发过渡效果。