在Vue的v-for中,key为什么不能使用index?
写在前端,主要是DOM相关操作和JS相关,我们都知道DOM操作是比较耗时的,所以我们在写前端相关代码的时候,如何减少不必要的DOM操作就成了前端优化的重要组成部分。VirtualDOM(虚拟DOM)在jQuery时代,基本上所有DOM相关的操作都是自己写的(当然博主没写过jQuery,可能博主太年轻,错过了jQuery大法时代),如何操作DOM,如何安排操作DOM的时机是决定性能的关键,而在Vue、React等框架盛行的时代,框架采用数据驱动的视图,封装了大量的DOM操作细节,让更多的DOM操作细节的优化已经从开发者自己的选择和控制转移到框架上。在学会使用框架之后,如果想更深入地学习框架,就需要了解框架封装的底层原理。其中一个很核心的部分就是虚拟DOM(virtualDOM)什么是虚拟DOM?简而言之,就是通过JS模拟DOM结构。纠结用什么JS数据结构来模拟DOM,并没有一套标准。只要能完整覆盖DOM的所有结构,下面就用更通用的方式来演示一下。通过对DOM结构的分析,我们可以用tag表示DOM节点的类型,props表示DOM节点的所有属性,包括style,class等,children表示子节点(没有子节点表示内容),所以我们把整个DOM都用JS模拟出来,然后呢?那就看下一章吧~~~//DOM嘿嘿~~我是义乌
//VDOMletvdom={tag:'div',props:{classname:'container',},children:[{tag:'h1',props:{classname:'title',style:{color:'black'}},孩子们:'HeiHei~~'},{tag:'div',props:{classname:'inner-box',},children:[{tag:'span',props:{classname:'myname'},children:'我是Yimwu'}]}]}虚拟DOM的作用当我们可以在JS中模拟DOM结构时,我们就可以通过JS优化DOM操作。如何优化呢?这时,差是时候让法律现身了当我们通过JS修改DOM时,不会直接触发DOM更新,而是会先生成一个新的虚拟DOM,然后使用diff算法与修改前生成的虚拟DOM进行比较,找出需要修改的点被修改。最后才是真正的DOM更新操作。Vue源码中diff算法的patch.js路径。Vue中diff算法的相关代码主要在patch.js文件中。路径如下图所示。patch函数1.如果新节点不存在(vnode未定义),直接执行destroyhook返回2.如果旧节点不存在(oldVnode未定义),直接创建新节点3.如果新节点和老节点存在,进入下一级判断,比较节点4.使用sameVnode函数判断新节点和老节点是否是同一个节点,如果相同则递进比较它们的子节点,如果是不同,直接重新创建新节点patchVnode函数1、如果新节点是文本节点(isUndef(vnode.text)===false)并且新旧节点的文本不同(oldVnode.text!==vnode.text),则直接设置(setTextContent)元素的文本(ele)2.如果新节点不是文本节点,则分为以下几种情况2.1。如果新老节点都有子节点,调用updateChildren更新子节点2.2,如果只有新节点有子节点,直接添加子节点(addVnode)2.3,如果只有老节点有子节点,直接删除子节点(removeVnodes)2.4。如果旧节点有文本,删除文本(setTextContext)。updateChildrenupdateChildren函数使用双端差异。所谓双端,即同时从新旧节点的两端向中间进行比较。比较步骤如下:1、新起始节点vs旧起始节点,若相同则直接遍历其子节点,调用patchVnode比较子元素差异,指针向前移动一步2、新结束nodevsoldendnode,如果相同,直接遍历它的children,调用patchVnode比较子元素差异,指针往前走一步3.oldstartnodevsnewendnode,如果相同,先移动将新的结束节点移动到旧起始节点的前一个位置,然后遍历它的子节点,调用patchvnode比较子元素差异,指针向前移动一步4.旧结束节点vs新开始节点,如果相同,先将新开始节点移动到旧结束节点后面的位置,然后遍历它的子节点,并调用patchVnode比较子元素差异,指针前移5步。如果前面4种情况都没有命中,则遍历新节点,将子节点与的子节点一一比较旧节点,比较会逐一遍历。如果不匹配,则直接重建元素diff算法中的Key值是从diff算法的updateChildren函数中得知的。双端diff算法会将新的开始和结束节点与旧的开始和结束节点进行比较。当没有匹配到的时候,就会用全遍历的方式逐一进行比较,那么这时候key就发挥作用了。当我们从新节点开始遍历节点,与旧节点进行匹配时,如果key匹配,则说明该元素只是一个位置出现。移动,直接调整位置并查看其子节点(sameVnode),无需完全重建元素,大大节省性能v-for中的key值可以是index吗?答案当然是否定的。例如,让我们看一下以下两个vdom。从num值可以看出,新旧vdoms是由两个数组以相反的顺序生成的。正常的vdom安装方式应该是简单的改一下顺序,直接复用3个元素。当我们使用索引作为键时,情况就不同了。由于索引总是从0开始,所以这两个vdom的key值从头到尾看起来都是一样的,这就导致我们比较key值的时候,会发现每一个都匹配,然后patchVnode它的child节点。这时,由于props不同,即num不同,所以会触发对应响应值的更新机制,过程中会调用多个更新相关的钩子函数。如果定义的属性很多,触发更新会造成非常大的性能损失,因此,在使用v-for时,建议使用id等唯一标识的字段,不要使用index,避免不必要的性能损失!constoldVdom={tag:"div",children:[{tag:"div",key:0,num:1},{tag:"div",key:1,num:2},{tag:"div"}",key:2,num:3},]}constnewVdom={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算法中的真实作用,也可以从更底层的角度理解为什么不推荐索引作为key的BestPractices!