首页 > 其他分享 >在vue的v-for中,key为什么不能用index?

在vue的v-for中,key为什么不能用index?

时间:2022-10-27 12:11:37浏览次数:70  
标签:index vue DOM children tag key div 节点

写在前面

在前端中,主要涉及的基本上就是 DOM的相关操作 和 JS,我们都知道 DOM 操作是比较耗时的,那么在我们写前端相关代码的时候,如何减少不必要的 DOM 操作便成了前端优化的重要内容。

虚拟DOM(virtual DOM)

在 jQuery 时代,基本上所有的 DOM 相关的操作都是由我们自己编写(当然博主是没有写过 jQuery 滴,可能因为博主太年轻了吧,错过了 jQuery 大法的时代),如何操作 DOM, 操作 DOM 的时机应该如何安排成了决定性能的关键,而到了 Vue、React 这些框架盛行的时代,框架采用数据驱动视图,封装了大量的 DOM 操作细节,使得更多的 DOM 操作细节的优化从开发者自己抉择、控制转移到了框架内部,那么在学会使用框架后,如果想要更加深入学习框架,那就需要搞懂框架封装的底层原理,其中非常核心的一部分就是虚拟DOM(virtual DOM)

什么是虚拟 DOM

简而言之,就是通过 JS 来模拟 DOM 结构,关于纠结以什么 JS 数据结构来模拟 DOM 并没有一套标准,只要能完全覆盖 DOM 的所有结构即可,下面以较为通用的方式演示一下。

通过对 DOM 结构的分析,我们可以用 tag 表示 DOM 节点的类型,props 表示 DOM 节点的所有属性,包括 style、class 等,children 表示子节点(没有子节点则表示内容),这样子我们就把整个 DOM 通过 JS 模拟出来了,然后呢? 然后看看下一章~~~

// DOM
<div class="container">
  <h1 style="color: black;" class="title">HeiHei~~</h1>
  <div class="inner-box">
    <span class="myname">I am Yimwu</span>
  </div>
</div>

// VDOM
let vdom = {
  tag: 'div',
  props: {
    classname: 'container',
  },
  children: [
    {
      tag: 'h1',
      props: {
        classname: 'title',
        style: {
          color: 'black'
        }
      },
      children: 'HeiHei~~'
    },
    {
      tag: 'div',
      props: {
        classname: 'inner-box',
      },
      children: [
        {
          tag: 'span',
          props: {
            classname: 'myname'
          },
          children: 'I am Yimwu'
        }
      ]
    }
  ]
}

虚拟 DOM 的作用

当我们能够在 JS 中模拟出 DOM 结构后,我们就可以通过 JS 来对 DOM 操作进行优化了,怎么优化呢,这个时候 diff 算法就该登场了。当我们通过 JS 对 DOM 进行修改后,并不会直接触发 DOM 更新,而是会先生成一个新的虚拟 DOM,然后利用 diff 算法与修改前生成的虚拟 DOM 进行比较,找出需要修改的点,最后进行真正的 DOM 更新操作

Vue 源码中的 diff 算法

patch.js 路径

Vue 中的 diff 算法相关代码主要在 patch.js 文件中,路径如下图

image.png

patch 函数

image.png

1、如果新节点不存在(vnode is undefined),直接执行 destroyhook 并返回

2、如果旧节点不存在(oldVnode is undefined),直接创建新节点

3、如果新节点与旧节点都存在则进入下一层判断,对节点进行比对

参考:前端vue面试题详细解答

image.png

4、使用 sameVnode 函数判断新节点与旧节点是否为相同的节点,如果相同则递进对比其子节点,如果不同则直接重新创建新节点

patchVnode 函数

image.png

1、如果新节点为文本节点(isUndef(vnode.text) === false) 且 新旧节点文本不同(oldVnode.text !== vnode.text),则直接设置(setTextContent)元素(ele)的文本

2、如果新节点不是文本节点,则又分为以下几种情况

2.1、如果新节点和旧节点都有 child,则调用 updateChildren 更新子节点
2.2、如果只有新节点有 child,则直接添加子节点(addVnode)
2.3、如果只有旧节点有 child,则直接删除子节点(removeVnodes)
2.4、如果旧节点有 text,则删除 text(setTextContext)

updateChildren

image.png

updateChildren 函数采用的是双端 diff,所谓双端,也就是从新旧节点的两端同时向中间比较,比较的步骤如下:

1、新开始节点 vs 旧开始节点,如果相同则直接遍历其 children,调用 patchVnode 比较子元素差异,指针往前走一步

2、新结束节点 vs 旧结束节点,如果相同则直接遍历其 children,调用 patchVnode 比较子元素差异,指针往前走一步

3、旧开始节点 vs 新结束节点,如果相同则先把新结束节点移动到旧开始节点的前一个位置,然后遍历其 children,调用 patchVnode 比较子元素差异,指针往前走一步

4、旧结束节点 vs 新开始节点,如果相同则先把新开始节点移动到旧结束节点的后一个位置,然后遍历其 children,调用 patchVnode 比较子元素差异,指针往前走一步

5、若前面4种情况都没有命中,则将遍历新节点,将子节点组个与旧节点的子节点进行一一比较,逐个遍历对比,没有匹配到的则直接重建元素

diff 算法中的 Key 值

从 diff 算法的 updateChildren 函数中我们知道,采用双端 diff 算法会进行新的开始、结束节点和旧的开始、结束节点做对比,当都没有匹配上的时候会采用完全遍历的方式进行一一比较,那么这个时候 key 就发挥出作用了,当我们从新的节点中遍历节点,拿去和旧节点匹配时,如果 key 匹配上的话,那么就表明该元素只是位置发生了移动,直接调整位置后对其子节点进行(sameVnode)检查即可,而不需要完全重建元素,大大节省了性能。

v-for 中 key 值是否可以为 index

答案当然是不可以,举个例子,我们来看下面两个 vdom,从 num 值我们可以发现,新、旧两个 vdom 是两个顺序相反的数组生成的 vdom,安装正常的方式,应该是简单调换一下顺序,直接复用3个元素即可,而当我们以 index 作为 key 时,情况就不同了,由于 index 永远都是从 0 开始,所以这两个 vdom 的 key 值从开始到结束,看起来都是相同的,这就导致了当我们去对比 key 值的时候会发现他们每个都是匹配的,然后对其子节点进行 patchVnode,这个时候由于 props 不同,即 num 不同,因此会触发对应的响应式值的更新机制,而且在这个过程中还会调用多个更新相关的钩子函数,如果定义的属性非常多的话,触发更新将会导致非常大的性能损耗,因此,在使用 v-for 的时候,建议使用类似 id 这种唯一标识的字段替代 index,避免不必要的性能损耗!

const oldVdom = {
  tag: "div",
  children: [
    {
      tag: "div",
      key: 0,
      num: 1
    },
    {
      tag: "div",
      key: 1,
      num: 2
    },
    {
      tag: "div",
      key: 2,
      num: 3
    },
  ]
}
const newVdom = {
  tag: "div",
  children: [
    {
      tag: "div",
      key: 2,
      num: 3
    },
    {
      tag: "div",
      key: 0,
      num: 1
    },
    {
      tag: "div",
      key: 1,
      num: 2
    },
  ]
}

总结

对于 VDOM 以及 diff 算法的学习,体会到了前端对于性能的极致追求,通过通读 vdom 源码,基本能够从更加深刻的角度去理解采用 VDOM 的目的,以及 key 值在 diff 算法中的真正作用,也能够从更加底层的角度理解为什么不推荐使用 index 作为 key 这个 Best Practices!

标签:index,vue,DOM,children,tag,key,div,节点
From: https://www.cnblogs.com/bbxiaxia1998/p/16831786.html

相关文章

  • Vue项目实现导入导出Excel表格功能
    前提:在我的项目中我使用的是ElementUi前端UI框架,用到的是里面的Upload上传组件。第一步:需要安装三个依赖npminstall-Sfile-saverxlsx (这里其实安装了2个......
  • 一、认识Vue-单文本组件
        在大多数启用了构建工具的Vue项目中,我们可以使用一种类似HTML格式的文件来书写Vue组件。    它被称为单文件组件 (也被称为 *.vue 文件,英文......
  • vue跨域简易版
    当后端接口没有跨域功能时且无法去修改(比如调用第三方接口),就需要前端自己实现跨域功能。vue-cli项目1.在根目录的vue.config.js中配置哪些请求需要转发到没有跨域功能的接......
  • hansontable在vue中的基本使用
    代码Test.vue<template><divid="hansontable"><hot-table:data="data":settings="hotSettings"ref="hotTableRef"></hot-table><......
  • Cold Turkey简易使用教程(版本Cold Turkey Version 2.1.3)
    只要一开电脑,东看看,西看看,时间没了。像我这么没自制力的人,怎么才能更好的管制自己呢?答:可以试试ColdTurkey但是,不得不说,老外做的这个软件,在使用上没有很好的中文教程,难以......
  • vue之hello
    <!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><metaname="viewport"content="width=device-width,initial-scale=1.0"><metahttp-......
  • 8_vue是如何进行数据代理的
    在了解了关于js当中的Object.defineProperty()这个方法后,我们继续对vue当中的数据代理做一个基于现在的解析建议观看之前先了解下js当中的Obejct.defineProperty()链接地......
  • vue3-setup 的参数
    setup(props,context){}第一个参数:    props,是一个对象,包含父组件传递给子组件的所有数据。在子组件中使用props进行接收,包含配置声明并传入的所有的属性的......
  • Tauri-Vue3桌面端聊天室|tauri+vite3仿微信|tauri聊天程序EXE
    基于tauri+vue3.js+vite3跨桌面端仿微信聊天实例TauriVue3Chat。tauri-chat运用最新tauri+vue3+vite3+element-plus+v3layer等技术跨桌面端仿微信|QQ聊天程序EXE。基本实......
  • Vue的双向绑定 v-model的原理
    Vue的双向绑定v-model的原理使用V-model进行绑定v-model的效果就是用户在输入的时候实际上实在修改txtVal的值,修改成用户输入的内容<inputtype="text"v-model="txt......