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

2023-05-16

写在前面

在前端中,主要涉及的基本上就是 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'
        }
      ]
    }
  ]
}

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

虚拟 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、如果新节点与旧节点都存在则进入下一层判断,对节点进行比对

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!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

  • 确定 Java HashMap 中最低可用键的最快方法?

    想象一下这样的情况 我有一个HashMap
  • 字典无法识别浮点键

    我有一本叫做 G 的字典 当我输入G keys 输出的一个示例是 gt gt gt G keys 1490775 0 12037425 0 1493775 0 12042675 0 1481055 0 12046305 0 1503105
  • 使用字典中的键反转多个值

    我对 Python 和整体编程还很陌生 所以请耐心等待 我有一本字典 Male Female Eunuch 作为值和这些作为键的不同名称 Persons Hodor Male Tyrion Male Theon Male Arya Fema
  • 获取与 python dict 中的 max(value) 对应的键[重复]

    这个问题在这里已经有答案了 让我们考虑一下 键 值 对的示例字典 如下所示 dict1 a 10 x 44 f 34 h 89 j 90 d 28 g 90 dict2 a 10 x 44 f 34 h 89 j 90 d 28 在字典中的
  • 在 R 中向多直方图添加关键图例

    如何在下面的图中添加关键图例 我希望在右上角的某个地方有一个关键图例 其中有两个短水平颜色条 红色的应该说 整形手术出了问题 蓝色的应该说 德国 我使用以下代码来生成该图 bar2 lt read table div ana mut bar
  • 如何通过 RSA 生成唯一的公钥和私钥

    我正在构建一个自定义购物车 其中 CC 编号和到期日期将存储在数据库中直至处理 然后删除 我需要加密这些数据 显然 我想使用 RSACryptoServiceProvider 类 这是我创建密钥的代码 public static void
  • 有没有办法让字典键成为范围?

    如果这很明显 请原谅我 但我对 Python 非常非常陌生 我已经找到了从字典中获取多个键的方法 但这不是我想要做的 基本上我正在寻找这样的东西 my dict 1 10 foo 11 20 bar 91 100 baz 但其中的键实际上不
  • 如何将字符转换为等效的 System.Windows.Input.Key Enum 值?

    我想写一个这样的函数 public System Windows Input Key ResolveKey char charToResolve Code goes here that resolves the charToResolve
  • Delphi7,按向上键时进行形状​​跳跃

    我想在玩家按下UP键时进行形状 跳跃 所以我能想到的最好的就是这个 但我使用的方法很糟糕并且有问题 形状坐标 shape1 top 432 procedure TForm1 FormKeyDown Sender TObject var Ke
  • Python 字典键中的空格

    我知道 Python 字典键中可以有空格 但这被认为是糟糕的编程吗 我在 PEP 中找不到任何与此相关的内容 编辑以澄清 在我正在做的一个项目中 我正在研究解析 Apache 的记分板输出的东西mod status 请参阅下面的示例输出 我
  • 如何使用包含空格的键从Redis获取值?

    使用 telnet 我输入这样的命令行命令 get field with spaces get field with spaces get field with spaces 这三个都返回相同的错误 ERR wrong number of
  • 根据 Multimap Google Guava 中的键按升序对数据进行排序

    我创建了一个多重地图 Multimap
  • PHP 对齐数组键值

    我在 Google 上搜索了两天 并尝试查看 PHP 手册 但我仍然不记得那个对齐 PHP 数组键值的函数 我正在寻找的只是需要这个的函数 Array 0 gt 1 3 gt 2 4 gt 3 7 gt 4 9 gt 5 并将其转换成这样
  • 将两个键与 std::map 一起使用的最佳方法是什么?

    我有一个std map我用来存储 x 和 y 坐标的值 我的数据非常稀疏 所以我不想使用数组或向量 这会导致内存的大量浪费 我的数据范围从 250000到250000 但我最多只有几千个点 目前我正在创建一个std string与两个坐标
  • PHP 8.0 中处理未定义数组键的最佳方法[关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 几个月前 我升级到了 PHP 8 0 和许多开发人员一样 我也遇到了这个通知变成警告的麻烦 我不明白处理这个问题的正确方法 所以我想知道如何解决
  • Outlook 添加、文本框、删除\退格键不起作用

    我开发了一个 Outlook 插件 自定义任务窗格 在用户控件中带有 Web 浏览器 当我在网络浏览器的文本框中写入内容时 退格键或删除按钮旁边的所有功能都运行良好 但我无法使用这些键 我是否遗漏了什么 我迟到了几年 但我设法解决了这个问题
  • 在网站上以多种形式输入密钥

    我有一个有两种表格的网站 一个用于搜索 另一个用于登录 当我使用回车键提交时 搜索总是被调用 因为它是页面上的第一个表单 我想要做的是对回车键进行编程 以在某个文本框获得焦点时单击某个按钮 我使用 asp textbox 和 asp but
  • 如何使用jq通配符

    我有以下 json details car bmw addresses ext 118 21 8 0 29 version 4 addr 89 Psr version 6 addr 56 apT The key ext 118 21 8 0
  • Google App Engine 密钥中允许使用哪些字符?

    在测试我的 Google App Engine 应用程序时 我搜索包含应用程序引擎密钥的链接 例如 story ag5yZXBsaWUtdGVzdGluZ3IMCxIFU3RvcnkY w0M 这些键中允许使用哪些字符 我一直在使用正则表达
  • 返回不包括指定键的字典副本

    我想创建一个函数 返回字典的副本 不包括列表中指定的键 考虑这本词典 my dict keyA 1 keyB 2 keyC 3 致电without keys my dict keyB keyC 应该返回 keyA 1 我想用一行简洁的字典理

随机推荐

  • PX4 与 MAVROS 实现offboard

    目录 一 虚拟机仿真环境 1 创建工作空间 2 创建ROS节点功能包 3 运行PX4的gazebo仿真 4 启动PX4与Mavros之间的连接 二 真机控制 1 硬件连接 2 软件设置 3 出现问题 Ubuntu xff1a 20 04 x
  • AprilTag_ros的使用

    目录 前言 一 usb摄像头的安装和使用 二 AprilTag ros包的安装 三 单目摄像机的标定 四 AprilTag ros的使用 五 其他 前言 平台 xff1a VM虚拟机 ROS版本 xff1a noetic Ubuntu xf
  • make px4_sitl_default gazebo 建立PX4仿真环境的各种坑

    前言 xff1a 平台 xff1a VM 虚拟机 Ubuntu18 04 gazebo9 一 执行组件更新总是各种中断 git submodule update init recursive 众所周知这是墙墙的故事 xff0c 所以进行了机
  • Jetson nano刷机安装系统

    1 下载系统镜像 可以到官网上下载镜像 xff0c 英伟达官网 在上面选择合适的镜像文件下载 2 格式化内存卡 需要下载格式化tf卡的工具SD Memory Card Formatter xff0c 读者可以也自行下载 格式化时候就像下图
  • 继电器的使用

    继电器使用 继电器基本说明 继电器就相当于一个开关 xff0c 接在任意线上 xff0c 通过控制信号下控制通断 xff1b 一般是断开状态 xff0c 此时线就断开了 xff0c 没导通 xff1b 在控制信号作用下继电器闭合 xff0c
  • 在树莓派上使用火焰,声音,震动,光敏传感器

    作为一个软件工程专业的学生 xff0c 对传感器等硬件的使用一直不太顺手 xff0c 而在树莓派使用Python的RPi GPIO xff0c 进行传感器等硬件的使用却是非常方便 而且使用树莓派这个网络功能强大的控制中心 xff0c 其在物
  • UCOSIII---信号量

    UCOSIII 信号量 概述PV原语函数接口创建信号量等待信号量释放信号量 例程注意 优先级反转概述解决方案注意事项 概述 信号量常用于任务的同步 xff0c 通过该信号 xff0c 就能够控制某个任务的执行 xff0c 这个信号具有计数值
  • 十分钟读懂『卡尔曼滤波算法』

    我是勤劳的搬运工 xff0c 转自 xff1a 1 http blog csdn net karen99 article details 7771743 2 http blog csdn net tudouniurou article de
  • Pixhawk基于Radio地面站发送指令

    xfeff xfeff px4原生固件提供offboard飞行模式 xff0c Offboard模式是使用外部电脑 xff08 软件 xff09 与pixhawk相连 xff0c 并进行控制 在室内室外都可使用该模式 xff0c 标准代码都
  • IDEA中SpringBoot出错问题

    1 新建项目时 xff0c 出现 Error java 无效的标记 parameters 或者 Error java 无效的源发行版 13 等这些问题时 xff0c 需要看下project setting中的各种配置 xff0c 注意以下图
  • 程序 = 数据结构 + 算法

    我们编写程序的目的就是与真实世界交互 xff0c 解决真实世界的问题 xff0c 帮助真实世界提高运行效率与改善运行质量 所以我们就需要对真实世界事物体的重要属性进行提炼 xff0c 并映射到程序世界中 xff0c 这就是所谓的对真实世界的
  • C++中的::

    34 34 在C 43 43 中表示作用域 xff0c 和所属关系 34 34 是运算符中等级最高的 xff0c 它分为三种 xff0c 分别如下 xff1a 一 作用域符号 xff1a 作用域符号 的前面一般是类名称 xff0c 后面一般
  • Ubuntu16桌面版安装realsense SDK

    Ubuntu16桌面版安装realsense SDK 1 下载realsense master 官网下载连接 xff1a https github com IntelRealSense librealsense 2 解压realsense
  • 自动驾驶中使用到的坐标转换

    一 简介 1 1 地心地固直角坐标系 xff08 ECEF xff09 也叫地心地固直角坐标系 其原点为地球的质心 xff0c x轴延伸通过本初子午线 xff08 0度经度 xff09 和赤道 xff08 0deglatitude xff0
  • 自动驾驶坐标转换-北东地/东北天两种导航坐标系与姿态转换

    一 坐标系 1 导航坐标系 常用的导航坐标系有北东地和东北天两种 两种坐标系的指向分别定义如下 xff1a 1 1 北东地坐标系 X轴 xff1a 指北 Y轴 xff1a 指东 Z轴 xff1a 指地 1 2 东北天坐标系 X轴 xff1a
  • DMA 中断 查询三者的区别

    1 DMA xff08 DIRECT MEMORY ACCESS xff09 即直接存储器存取 xff0c 是指外部设备不通过CPU而直接与系统内存交换数据的接口技术 要把外设的数据读入内存或把内存的数据传送到外设 xff0c 一般都要通过
  • Linux 下 i2c switch(选路芯片mux) — pca9548

    作者 xff1a 韩大卫 64 吉林师范大学 现有的关于i2c switch 资料非常少 即使阅读完官方的datasheet 也不能写出完全正确的操作 因为内核中的驱动本身不是那么完善的 还有一些资料是单片机编程的 xff0c 可惜在lin
  • 栈区的地址增长方向与buf地址的增长方向是两个完全不同的概念

    一 栈区的地址增长方向 要想验证栈区究竟是开口向上还是开口向下 xff0c 都进行先压变量a再压变量b的操作 xff0c 若a的首地址比b的首地址大则说明开口向下 xff0c 若b的首地址比a的首地址大 xff0c 则说明开口向上 xff0
  • [转]www-authenticate认证过程浅析

    一 www authenticate简介 www authenticate是早期的一种简单的 xff0c 有效的用户身份认证技术 很多网站验证都采用这种简单的验证方式来完成对客户端请求的数据的合法性进行验证 尤其在嵌入式领域中 xff0c
  • 在vue的v-for中,key为什么不能用index?

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