vue.js

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript类库 > vue.js > vue index和id区别

Vue中key为index和id的区别示例详解

作者:Lakeiedward

这篇文章主要介绍了Vue中key为index和id的区别详解,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

一、Diff算法

在了解key的作用之前,先简单认识一下diff算法👇

diff算法的特点是平级比较,采用双指针和递归的方式进行逐级比较。

Vue会有一个根节点,先判断根节点是否是文本节点,如果不是文本节点,则会判断是否都有儿子节点,如果都有并且新旧儿子节点不相等,此时就会比较这两个新旧儿子节点(updateChildren),在做比较的时候会有以下几种情况:

二、Key的作用

在比较两个节点的时候sameVnode(oldStartVnode, newStartVnode),主要根据key进行判断两个元素是否是一个元素,key不相同的话则说明不是同一个元素。使用key的时候尽量保持key的唯一性(这样可以优化diff算法)

动态列表添加的key的时候,要避免使用索引(index)!

接下来,我们使用数组渲染一组儿子节点小li,并且通过事件在数组的头部增加(unshift)一个数据;当key为index的时候,我们查看下图图片渲染的情况发现所有的小li都变化了,而key为id的时候,则只在li的最前面新加了一个小li,这就是diff算法根据key判断产生的差异性,具体在下面来看一看。

三、Key为Index

1) 图解

如下图,首先上面是旧节点,下面是新节点,新节点上在数组最前面新加了一个C节点,因为key是index,所以此时C的key还是0,但是文本是C,并不是A。

因为第一个新旧节点的key相同,所以此时会先进入到头头对比中,而不会进入到尾尾对比,在对比的过程中,会再次进入到patchVnode方法中判断新旧节点的文本是否一致,如果一致则直接复用,不一致则会对dom进行操作,将旧节点文本替换成新节点文本node.textContent = text

第一组对比完成之后,新旧节点的索引会依次增加,对比第二组,第二组的key也是一样的,会重复第一组的对比方式,最后将旧节点文本替换成新节点文本node.textContent = text

此时因为旧节点的开始索引和结束索引相等,则会退出while循环,根据判断新旧节点的开始和结束索引得出,最后一个剩余的新节点会插入(addVnodes)到A元素后面去。

此时更新就结束了,会发现进行了三次dom操作,虽然新旧节点除了新增的C节点,其他都是相同的,但是都没有复用原来的节点,而是直接使用textContent改变文本,所以index作为key不中!

2) 完整的步骤

看下一个完整的步骤:

四、Key为Id

1) 图解

如下图,新加的节点key为c,当进行sameVnode(oldStartVnode, newStartVnode)对比的时候,发现key不一样

则开始尾尾对比sameVnode(oldEndVnode, newEndVnode),此时key是一样的,则进入到patchVnode方法,判断新旧节点的文本是否一致,一致的话,就复用原来的节点了

对比完第一组,此时新旧节点的尾索引减1,还是尾尾相等,开始尾对比,重复上述的步骤,复用原来的旧节点,没有dom操作。

>

对比完第二组,旧节点的头索引和尾索引相等,则结束while循环,最后一个剩余的新节点会插入(addVnodes)到A元素前面去。

以上的步骤完成之后,只有最后一次执行了插入dom操作,优化了diff算法和减少了dom操作

2) 完整的步骤

完整的步骤:

五、源码

粘贴一下部分的Vue源码

1)sameVnode

只会判断key、 tag、是否有data的存在、是否是注释节点、是否是相同的input type,来判断是否可以复用这个节点。

function sameVnode(a, b) {
  return (
    a.key === b.key &&
    a.asyncFactory === b.asyncFactory &&
    ((a.tag === b.tag &&
      a.isComment === b.isComment &&
      isDef(a.data) === isDef(b.data) &&
      sameInputType(a, b)) ||
      (isTrue(a.isAsyncPlaceholder) && isUndef(b.asyncFactory.error)))
  )
}
function sameInputType(a, b) {
  if (a.tag !== 'input') return true
  let i
  const typeA = isDef((i = a.data)) && isDef((i = i.attrs)) && i.type
  const typeB = isDef((i = b.data)) && isDef((i = i.attrs)) && i.type
  return typeA === typeB || (isTextInputType(typeA) && isTextInputType(typeB))
}

2)patchVnode

如果新 vnode 不是文字 vnode

如果新旧 children 都存在(都存在 li 子节点列表,进入 )

如果有新 children 而没有旧 children

如果有旧 children 而没有新 children

如果新 vnode 是文字 vnode

function patchVnode(
  oldVnode,
  vnode,
  insertedVnodeQueue,
  ownerArray,
  index,
  removeOnly?: any
){
  ...
  // 判断新节点是不是text节点
  if (isUndef(vnode.text)) {
  // 不是text节点
    if (isDef(oldCh) && isDef(ch)) {
      // 老节点和新节点都有child,并且child不相等
      if (oldCh !== ch)
        updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
    } else if (isDef(ch)) {
      // 新节点有child,老节点没有,则新增
      if (__DEV__) {
        checkDuplicateKeys(ch)
      }
      if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')
      addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
    } else if (isDef(oldCh)) {
      // 老节点有child,新节点没有,则删除
      removeVnodes(oldCh, 0, oldCh.length - 1)
    } else if (isDef(oldVnode.text)) {
      nodeOps.setTextContent(elm, '')
    }
  } else if (oldVnode.text !== vnode.text) {
    // 是text节点并且文本不一样,就把旧的文本替换成新的文本
    nodeOps.setTextContent(elm, vnode.text)
  }
  ...  
}

Tips: 儿子节点不是文本时,一方有儿子,一方没有儿子(删除、添加),两方都有儿子,则进入diff算法对比

六、总结

您可能感兴趣的文章:
阅读全文