剖析vue常见问题(三)之vue中key的作用和原理

2023-11-13

背景:说到vue中key的作用,大家都知道它可以唯一的确定一个dom元素,从而执行diff算法时更加高效,但是想更加详细的知道具体原因,我们还是需要从源码入手,详见源码:src/core/vdom/patch.js中的updateChildren()方法,当然我们也用demo来说明,demo如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vue key</title>
</head>
<body>
    <div>你知道vue中key的作用和工作原理吗?说说你对它的理解。源码中找答案:src/core/vdom/patch.js中的updateChildren()方法</div>


    <div id="demo">
       <!--此处可以通过去掉key和添加key在源码中打断点来对比调试-->
       <p v-for="item in items" :key="item">{{item}}</p>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
        //创建实例
        const app = new Vue({
            el: '#demo',
            data:{ items:['a','b','c','d','e'] },
            mounted () {
                setTimeout(()=>{
                    this.items.splice(2,0,'f')
                },2000);
            },
        });
    </script>
</body>
</html>

源码中updateChildren()方法如下,可以在第17行while处加上断点进行调试,代码详细如下:

function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
    let oldStartIdx = 0
    let newStartIdx = 0
    let oldEndIdx = oldCh.length - 1
    let oldStartVnode = oldCh[0]
    let oldEndVnode = oldCh[oldEndIdx]
    let newEndIdx = newCh.length - 1
    let newStartVnode = newCh[0]
    let newEndVnode = newCh[newEndIdx]
    let oldKeyToIdx, idxInOld, elmToMove, refElm

    // removeOnly is a special flag used only by <transition-group>
    // to ensure removed elements stay in correct relative positions
    // during leaving transitions
    const canMove = !removeOnly
    //在下面while处加上断点进行调试
    while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
      if (isUndef(oldStartVnode)) {
        oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
      } else if (isUndef(oldEndVnode)) {
        oldEndVnode = oldCh[--oldEndIdx]
      } else if (sameVnode(oldStartVnode, newStartVnode)) {
        /*前四种情况其实是指定key的时候,判定为同一个VNode,则直接patchVnode即可,分别比较oldCh以及newCh的两头节点2*2=4种情况*/
        patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)
        oldStartVnode = oldCh[++oldStartIdx]
        newStartVnode = newCh[++newStartIdx]
      } else if (sameVnode(oldEndVnode, newEndVnode)) {
        patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue)
        oldEndVnode = oldCh[--oldEndIdx]
        newEndVnode = newCh[--newEndIdx]
      } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
        patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue)
        canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
        oldStartVnode = oldCh[++oldStartIdx]
        newEndVnode = newCh[--newEndIdx]
      } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
        patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue)
        canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
        oldEndVnode = oldCh[--oldEndIdx]
        newStartVnode = newCh[++newStartIdx]
      } else {
        /*
          生成一个key与旧VNode的key对应的哈希表(只有第一次进来undefined的时候会生成,也为后面检测重复的key值做铺垫)
          比如childre是这样的 [{xx: xx, key: 'key0'}, {xx: xx, key: 'key1'}, {xx: xx, key: 'key2'}]  beginIdx = 0   endIdx = 2  
          结果生成{key0: 0, key1: 1, key2: 2}
        */
        if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
        /*如果newStartVnode新的VNode节点存在key并且这个key在oldVnode中能找到则返回这个节点的idxInOld(即第几个节点,下标)*/
        idxInOld = isDef(newStartVnode.key) ? oldKeyToIdx[newStartVnode.key] : null
        if (isUndef(idxInOld)) { // New element
          /*newStartVnode没有key或者是该key没有在老节点中找到则创建一个新的节点*/
          createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)
          newStartVnode = newCh[++newStartIdx]
        } else {
          /*获取同key的老节点*/
          elmToMove = oldCh[idxInOld]
          /* istanbul ignore if */
          if (process.env.NODE_ENV !== 'production' && !elmToMove) {
            /*如果elmToMove不存在说明之前已经有新节点放入过这个key的Dom中,提示可能存在重复的key,确保v-for的时候item有唯一的key值*/
            warn(
              'It seems there are duplicate keys that is causing an update error. ' +
              'Make sure each v-for item has a unique key.'
            )
          }
          if (sameVnode(elmToMove, newStartVnode)) {
            /*如果新VNode与得到的有相同key的节点是同一个VNode则进行patchVnode*/
            patchVnode(elmToMove, newStartVnode, insertedVnodeQueue)
            /*因为已经patchVnode进去了,所以将这个老节点赋值undefined,之后如果还有新节点与该节点key相同可以检测出来提示已有重复的key*/
            oldCh[idxInOld] = undefined
            /*当有标识位canMove实可以直接插入oldStartVnode对应的真实Dom节点前面*/
            canMove && nodeOps.insertBefore(parentElm, newStartVnode.elm, oldStartVnode.elm)
            newStartVnode = newCh[++newStartIdx]
          } else {
            // same key but different element. treat as new element
            /*当新的VNode与找到的同样key的VNode不是sameVNode的时候(比如说tag不一样或者是有不一样type的input标签),创建一个新的节点*/
            createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)
            newStartVnode = newCh[++newStartIdx]
          }
        }
      }
    }
    if (oldStartIdx > oldEndIdx) {
      /*全部比较完成以后,发现oldStartIdx > oldEndIdx的话,说明老节点已经遍历完了,新节点比老节点多,所以这时候多出来的新节点需要一个一个创建出来加入到真实Dom中*/
      refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
      addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
    } else if (newStartIdx > newEndIdx) {
      /*如果全部比较完成以后发现newStartIdx > newEndIdx,则说明新节点已经遍历完了,老节点多余新节点,这个时候需要将多余的老节点从真实Dom中移除*/
      removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
    }
  }

通过对比循环添加key和不添加key的调试结果我们可以得出以下结论:

1.不添加key的时候,会更新3次dom并且做一次创建插入工作

2.如果添加了key,只做了一次创建插入行为

综上可以知道,key的作用如下:

1.key的主要作用是为了高效的更新虚拟dom,其原理是vue在patch过程中通过key可以精准判断两个节点是否为同一个节点,从而避免频繁更新,使得整个patch过程更加高效,减少dom操作,提高性能。

2.通过设置key还可以避免一些bug.

3.vue中使用相同标签名元素的过渡切换时也使用key属性,主要目的也是为了让vue可以区分它们,否则vue只会替换其内部属性而不会触发过渡效果。

以上就是vue中key的作用以及其原理,希望对大家有所帮助。

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

剖析vue常见问题(三)之vue中key的作用和原理 的相关文章

  • Delphi7,按向上键时进行形状​​跳跃

    我想在玩家按下UP键时进行形状 跳跃 所以我能想到的最好的就是这个 但我使用的方法很糟糕并且有问题 形状坐标 shape1 top 432 procedure TForm1 FormKeyDown Sender TObject var Ke
  • 如何使用包含空格的键从Redis获取值?

    使用 telnet 我输入这样的命令行命令 get field with spaces get field with spaces get field with spaces 这三个都返回相同的错误 ERR wrong number of
  • PHP 对齐数组键值

    我在 Google 上搜索了两天 并尝试查看 PHP 手册 但我仍然不记得那个对齐 PHP 数组键值的函数 我正在寻找的只是需要这个的函数 Array 0 gt 1 3 gt 2 4 gt 3 7 gt 4 9 gt 5 并将其转换成这样
  • 在 Spring Boot 中转义 Yaml 中的 Map 键中的点

    我有以下 yml 配置 foo bar com a b baz com a c 通过以下类 Spring 尝试使用键 bar 和 baz 注入映射 将点视为分隔符 public class JavaBean private Map
  • Python - 查找字典中最长(最多单词)的键

    有没有一种方法可以快速查询字典对象以找到单词最多的键 所有键都是字符串类型 即 如果具有最大键的项目有五个单词 这是最大的键 3 我如何快速查询字典并返回 int 5 最好的 乔治娜 这将为您提供密钥 max d key lambda x
  • 如何从python字典中的给定名称获取键

    我有一个变量叫做 anime dict which contains a dictionary of lists of objects as shown below JI2212 Inu Yasha year 1992 rating 3 E
  • 在网站上以多种形式输入密钥

    我有一个有两种表格的网站 一个用于搜索 另一个用于登录 当我使用回车键提交时 搜索总是被调用 因为它是页面上的第一个表单 我想要做的是对回车键进行编程 以在某个文本框获得焦点时单击某个按钮 我使用 asp textbox 和 asp but
  • python随机字典键,并访问它[关闭]

    Closed 这个问题需要调试细节 help minimal reproducible example 目前不接受答案 import random Cards Spade 2 3 4 5 6 7 8 9 10 Jack Queen King
  • 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 我想用一行简洁的字典理
  • 如何更新 Firebase 中的节点密钥?

    如何重命名14 04 2017 node 没有用于重命名节点的 API 您必须获取节点的值 使用新名称将其保存到数据库并删除旧节点
  • 如何在 C++ 上检查某个键是否被按下

    我怎样才能检查 Windows 上是否按下了某个键 正如其他人提到的 没有跨平台的方法可以做到这一点 但在 Windows 上你可以这样做 下面的代码检查 A 键是否按下 if GetKeyState A 0x8000 Check if h
  • MySQL 中 INDEX、PRIMARY、UNIQUE、FULLTEXT 之间的区别?

    创建MySQL表时PRIMARY UNIQUE INDEX和FULLTEXT有什么区别 我将如何使用它们 差异 KEY or INDEX指的是普通的非唯一索引 索引的非不同值是允许的 因此索引may索引的所有列中包含具有相同值的行 这些索引
  • KeyboardEvent.keyCode 已弃用。这在实践中意味着什么?

    根据 MDN 我们绝对应该not正在使用 keyCode财产 它已被弃用 https developer mozilla org en US docs Web API KeyboardEvent keyCode https develope
  • 将平面数组拆分为分组子数组,其中包含输入数组中连续键的值

    我有一个数组array diff函数 如下所示 Array 0 gt world 1 gt is 2 gt a 3 gt wonderfull 5 gt in 6 gt our 正如您所看到的 键 3 和键 5 之间有一个间隙 即没有键 4
  • 搜索数组中的最高键/索引

    我怎样才能得到highest key index在一个数组中php questions tagged php 我知道如何为价值观做这件事 例如 我想从这个数组中得到10 as an integer value arr array 1 gt
  • 检查 key 是否存在 firebase Android

    我想检查 firebase 数据库中是否存在密钥 例如 我想查找关键的 upvotes 以查看它是否存在 Here is an exmaple upvotes key does not exist in here 现在我尝试检查密钥 upv
  • PHP 数组的最大键大小是多少?

    我正在生成关联数组 键值是 1 n 列的字符串连接 会回来咬我的钥匙有最大长度吗 如果是这样 我可能会停下来并采取不同的做法 它似乎仅受脚本内存限制的限制 快速测试后我得到了 128mb 的密钥 没问题 ini set memory lim
  • 自动单击按键一起[重复]

    这个问题在这里已经有答案了 如何一起单击自动按键而不是一个接一个地单击 就我而言 我想单击以下内容 CTRL ALT SHIFT 在 Autoit 中就是这样的 Send RCTRL Send RALT Send RSHIFT Send 但
  • 如何为键盘上的 xml 中的功能键设置不同的背景?

    我正在开发 Android 键盘应用程序 我尝试为普通键和功能键设置不同的背景 但它不起作用

随机推荐

  • 这是一篇关于如何成为一名AI算法工程师的长文

    点击上方 Datawhale 选择 星标 公众号 第一时间获取价值内容 这是一篇关于如何成为一名AI算法工程师的长文 经常有朋友私信问 如何学python呀 如何敲代码呀 如何进入AI行业呀 正好回头看看自己这一年走过的路 进行一次经验总结
  • Arduino离线安装Esp8266

    首先安装ArduinoIDE 可以直接去github官网下载 链接 安装完成后 打开Arduino IDE 文件 gt 首选项 在 其他开发板管理器网址 输入框中 填入以下网址 https www arduino me package es
  • mysql下载与安装

    1 网址 http www mysql com 2 进入网址 选择DOWNLOADS 下载 3 选择社区版 4 进入之后选择如下都可以 5 下载64位的 点进来都是最新的版本 点击如下可选择下载之前的版本 6 安装
  • 模型的过拟合与欠拟合

    过拟合与欠拟合 定义 过拟合 High Viarance 欠拟合 High Bias 误差 Error 产生原因 解决方案 防止欠拟合 防止过拟合 正则化 L0范数 L1范数 L2范数 讨论 参考 定义 首先要确定的两个概念是Underfi
  • gtk主题指南

    一 gtk主题指南 1 Widgets 2 Styles 3 Engines 4 gtkrc文件 1 修改构件的属性 2 每一构件的分为五种状态 3 风格绑定 1 将一种风格绑定到组件类 2 组件嵌套的方式widget class 如wid
  • 尚硅谷-谷粒商城P44Vue模板

    配置模板
  • 11个代码质量审核和管理工具,程序员必备!

    点击上方 全栈开发者社区 右上角 设为星标 如今 代码质量分析和审核已成为每个企业的基本流程 随着开源代码库使用的增加 安全性和代码质量对于构建高质量软件至关重要 不良的代码不仅会影响代码的可维护性 而且还会在某些情况下影响其性能 此外 更
  • TensorRT基础

    目录 1 1 TensorRT构建和编译一个模型 1 2 Interference 1 3 动态shape 1 4 ONNX TensorRT的核心在于对模型算子的优化 合并算子 利用GPU特性选择特定核函数等多种策略 通过tensorRT
  • Spring boot2.x配置redis缓存以及利用通配符删除缓存key

    环境 jdk1 8 window系统 需要安装redis maven项目 一 依赖 redis跟缓存依赖是必须的
  • 226. 翻转二叉树

    Definition for a binary tree node function TreeNode val left right this val val undefined 0 val this left left undefined
  • link标签介绍

    定义 HTML外部资源链接元素 规定了当前文档与外部资源的关系 该元素最常用于链接样式表 此外也可以被用来创建站点图标 比如PC端的 favicon 图标和移动设备上用以显示在主屏幕的图标 link标签属性 属性 值 描述 as audio
  • 84、PullToRefresh使用详解

    PullToRefresh使用详解 一 构建下拉刷新的listView http blog csdn net harvic880925 article details 17680305 PullToRefresh使用详解 二 重写BaseA
  • ipsec.conf 各配置含义

    IPsec conf 是 IPsec 协议的配置文件 它包含了各种网络安全相关的配置信息 下面是常见的一些配置项及其含义 conn 连接名 表示需要建立的安全连接的名称 left 或者 leftsubnet 本地端 IP 地址或子网地址 r
  • 【java基础一】string和list互转

    工作中常用到list和string互转 常见的互转方法 list转string 方法1使用java8 Stream流 List
  • vue-router路由的三种传参方式(params/query)

    路由传参 传参方式可分为params传参和query传参 其中params又可分为url中显示参数和不显示参数 1 params传参 显示参数 声明式 router link 该方式通过router link的to属性实现 子路由需要提前配
  • 异步复位同步释放原理

    深度揭秘异步复位同步释放原理 文章右侧广告为官方硬广告 与吾爱IC社区无关 用户勿点 点击进去后出现任何损失与社区无关 明天就放端午小长假了 提前祝大家节日快乐 腾讯官网已经给小编公众号开通了赞赏功能 在文章末尾 这个赞赏是针对原创作者的
  • Python高级培训第二次作业

    import operator class Cat object 创建类cat 继承与object def init self leg 设置 init 就上一个参数leg self leg leg 设置leg的值为leg def run s
  • VM 中ubuntu下----Eclipse ctrl+s 显示update conflict的问题

    VM 中ubuntu下 Eclipse ctrl s 显示update conflict的问题 VMworkstation中使用共享主机的方式 在eclipse下编辑windows下的文件 ctrl s时 显示update conflict
  • WPF 禁用TextBox的触摸后自动弹出虚拟键盘

    原文 WPF 禁用TextBox的触摸后自动弹出虚拟键盘 前言 问题 如下截图 TextBox 在触摸点击后 会自动弹出windows的虚拟键盘 如何 禁用键盘的自动弹出 调用虚拟键盘 通过调用TapTip exe或者osk exe 主动弹
  • 剖析vue常见问题(三)之vue中key的作用和原理

    背景 说到vue中key的作用 大家都知道它可以唯一的确定一个dom元素 从而执行diff算法时更加高效 但是想更加详细的知道具体原因 我们还是需要从源码入手 详见源码 src core vdom patch js中的updateChild