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

news2024/11/27 18:30:25

写在前面

在前端中,主要涉及的基本上就是 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、如果新节点与旧节点都存在则进入下一层判断,对节点进行比对

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!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/88471.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Yolov3目标检测BS交互式框架——火焰识别为例

每个模块都具有可扩展性&#xff0c;可以根据需求自行扩展。本例子仅用火焰识别&#xff0c;可以自行训练新的权重实现检测不同目标。可以进行内网搭建&#xff0c;无需要安装程序 B/S模式 VS B/S模式&#xff1a; /B/SC/S跨平台√&#xff08;要写多种&#xff09;维护成本低…

51单片机点亮LED灯

LED 灯 发光二极管&#xff08;Light Emitting Diode&#xff09;&#xff0c;主要用于照明、广告灯、指引灯、屏幕等场景。 LED 原理图 VCC 表示电源正极&#xff0c;当二极管的正极对应电源的正极&#xff0c;并且二极管的负极对应电源的负极时&#xff0c;二极管就会亮灯。…

反义寡核苷酸/司盘修饰/载米铂与核酸miR-34a/冰片修饰的丹参酮ⅡA阳离子脂质体的合成

小编今天这里分享的内容是反义寡核苷酸/司盘修饰/载米铂与核酸miR-34a/冰片修饰的丹参酮ⅡA阳离子脂质体的合成方法&#xff0c;一起来看&#xff01; 冰片(BO)修饰的丹参酮ⅡA(TA)阳离子脂质体TA-BCLPs方法&#xff1a; 采用乙醇注入法制备阳离子脂质体,通过正交设计,以粒径,…

SpringSecurity[5]-基于表达式的访问控制/基于注解的访问控制/Remember Me功能实现

上一篇:SpringSecurity[4]-访问控制url匹配/内置访问控制方法介绍/角色权限判断 链接:SpringSecurity[4]-访问控制url匹配/内置访问控制方法介绍/角色权限判断_豆虫儿的博客-CSDN博客 十一、基于表达式的访问控制 1.access()方法使用 之前学习的登录用户权限判断实际上底层…

Spring Security进阶学习

Spring Security整体架构 认证 认证核心组件的大体关系如下&#xff1a; Spring Security 中的认证工作主要由 AuthenticationManager 接口来负责&#xff0c;它处理来自框架其他部分的身份验证请求。其中还涉及到一些关键类&#xff0c;比如&#xff1a;AuthenticationProvi…

同城预约小程序上门服务上门理疗推拿按摩系统养生美容行业程序源码

在这个工作生活压力巨大的社会&#xff0c;大家恨不得一分钟掰成两半过&#xff0c;别提什么休闲娱乐了&#xff0c;能睡个饱觉就已经是奢侈了&#xff01;工作固然重要&#xff0c;身心的放松也需要重视&#xff0c;好在随着互联网&#xff0b;的发展&#xff0c;越来越多的行…

PMO在企业项目管理中的五个重要作用

PMO项目管理办公室是成功企业的关键管理工具。它对于推动项目的发展至关重要&#xff0c;以下是PMO的五个重要作用&#xff1a; 1、项目管理过程的标准化 PMO的主要目标在于方法、流程和工具的创建和标准化。 PMO 可能创建的模板包括&#xff1a; • 项目建议书模板。这有…

Java Optional 实用判空实用实战,优雅永不过时

平时我们很多实体类里面会嵌套实体类&#xff0c;实体里面还嵌套实体。 那么我们有时为了取出最里面的实体的某个值的时候&#xff0c;我们就不得不一层层剥开这个 让人流泪的洋葱&#xff0c; 一层层判断。 举例&#xff08;随便举的&#xff09;&#xff1a; 就像这么一个…

关于迭代器遍历及auto关键词

在使用vector容器或者字符串时&#xff0c;很经常会用到一些遍历操作&#xff0c;除了使用下标遍历之外&#xff0c;使用迭代器遍历也是超级方便&#xff0c;但是迭代器也有有一些小坑&#xff0c;一不注意就会编译出错&#xff0c;所以特意总结一下。 迭代器 迭代器很很多接口…

性能测试之nginx监控系统搭建

不同tomcat服务器的负载均衡 在Nginx服务器192.168.43.138上安装Nginx&#xff0c;&#xff08;安装教程在前几篇文章有详细描述 &#xff09;实现反向代理tomcat负载均衡 执行一下命令&#xff0c;关闭防火墙 systemctl disable firewalld.service systemctl stop firewall…

[附源码]Node.js计算机毕业设计防疫科普微课堂Express

项目运行 环境配置&#xff1a; Node.js最新版 Vscode Mysql5.7 HBuilderXNavicat11Vue。 项目技术&#xff1a; Express框架 Node.js Vue 等等组成&#xff0c;B/S模式 Vscode管理前后端分离等等。 环境需要 1.运行环境&#xff1a;最好是Nodejs最新版&#xff0c;我…

从外包被裁到拿到阿里Offer,多亏P8架构师的全套面试文档

引言 又是一年跳槽季&#xff0c;在疫情的影响下&#xff0c;今年的金三银四冷清不少。但无论如何&#xff0c;2020年招聘市场已经显示出了一个清晰的趋势&#xff0c;java开发岗面试越来越难&#xff0c;需求越来越少&#xff01;也更增加了游戏的“难度系数”。 跳槽时时刻刻…

99-数据结构与算法(上篇)

数据结构与算法数据结构和算法&#xff0c;一个非常古老的课题&#xff0c;工作的时候&#xff0c;一般只求程序能跑&#xff0c;并不太关注性能 一般情况下&#xff0c;我们尽量避坑&#xff0c;即避免这样&#xff1a;ArrayList Or LinkedList&#xff0c;哪个简单用哪个 实…

【Kubernetes】一主二从环境搭建,详细的图文描述

kubernetes&#xff0c;是一个全新的基于容器技术的分布式架构领先方案&#xff0c;是谷歌严格保密十几年的秘密武器----Borg系统的一个开源版本&#xff0c;于2014年9月发布第一个版本&#xff0c;2015年7月发布第一个正式版本。 kubernetes的本质是一组服务器集群&#xff0…

使用Java API操作HDFS

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录学习目标&#xff08;一&#xff09;了解HDFS Java API1、HDFS常见类与接口2、FileSystem的常用方法&#xff08;二&#xff09;编写Java程序访问HDFS1、创建Maven项…

Nacos 配置中心之长轮询--服务端

先回顾一下客户端和服务端交互的过程 服务端 入口 直接看长轮询的接口 ConfigController.listener PostMapping("/listener")Secured(action ActionTypes.READ, parser ConfigResourceParser.class)public void listener(HttpServletRequest request, HttpServ…

抓住三个关键因素,提高你的ASA广告效果!

​ 众所周知&#xff0c;App Store 作为 iOS 端的流量收口&#xff0c;旗下的 ASA 广告更是广告主在 iOS 生态投放广告的唯一渠道&#xff0c;所提供的四大广告位&#xff08;Today 标签、搜索标签、搜索结果和产品页面&#xff09;覆盖了用户访问的全路径&#xff0c;为广告主…

12月14日:跟着猫叔写代码api中的增删改查

首先在数据库中建立一个学生成绩信息表 DROP TABLE IF EXISTS bro_ceshiapi; CREATE TABLE bro_ceshiapi (id int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT id,name varchar(100) DEFAULT NULL COMMENT 姓名,class varchar(100) DEFAULT NULL COMMENT 班级,score decima…

[附源码]Python计算机毕业设计Django基于vuejs的文创产品销售平台app

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

学习Vue3 - 认识 Reactive 全家桶

reactive 用来绑定复杂的数据类型&#xff0c;例如&#xff1a;对象、数组 reactive 源码约束了我们的类型 他是不可以绑定普通的数据类型的&#xff0c;这样是不允许的&#xff0c;会报错 因此&#xff0c;如果绑定普通的数据类型&#xff0c;可以使用ref ref绑定对象或者…