for 循环时 key 的作用 及diff算法

写 React / Vue 项目时为什么要在列表组件中写 key,其作用是什么?

本文内容截选至:https://blog.csdn.net/weixin_42707287/article/details/113994483

  • 标准答案:vue和react 都是采用diff算法来对比新旧虚拟节点,从而更新节点,在vue的diff函数中(待会我们再来探究diff函数)在交叉对比中,当新节点跟旧节点头尾交叉对比没有结果时,会根据新节点的key区对比旧节点数组中的key,从而找到相应旧节点(这里对应的是一个key => index 的map映射)。如果没找到就认为是一个新增节点。而如果没有key,那么就会采用遍历查找的方式去找到对应的旧节点。一种是一个map映射,另一种是遍历查找。相比而言,map映射的速度更快。

  • 虚拟dom及diff算法

  • 虚拟dom是目前最流行的框架vue和react都使用到的一种技术,能帮助框架提升渲染性能,提示用户体验。

    1.什么是虚拟dom:虚拟dom就是一个普通的js对象。是一个用来描述真实dom结构的js对象,因为他不是真实dom,所以才叫虚拟dom。
    2.虚拟dom的作用:
    传统dom数据发送变化的时候,需要不断的去操作dom,才能更新dom的数据,虽然后面出现了模板引擎这种东西,可以让开发者一次性去更新多个dom。但模板引擎依旧没有一种可以追踪状态的机制,当引擎内某个数据发生变化时,开发者依然要操作dom去重新渲染整个引擎。
    而虚拟dom可以很好的跟踪当前dom状态,因为他会根据当前数据生成一个描述当前dom结构的虚拟dom,然后数据发送变化时,又会生成一个新的虚拟dom,而这两个虚拟dom恰恰保存了变化前后的状态。然后通过diff算法,计算出两个前后两个虚拟dom之间的差异,得出一个更新的最优方法(哪些发生改变,就更新哪些)。可以很明显的提升渲染效率以及用户体验
    因为虚拟dom是一个普通的javascript对象,故他不单单只能允许在浏览器端,渲染出来的虚拟dom可同时在node环境下或者weex的app环境下允许。有很好的跨端性。

diff算法:

虚拟DOM中,在DOM的状态发生变化时,虚拟DOM会进行Diff运算,来更新只需要被替换的DOM,而不是全部重绘。

  • 比较两个节点是否是相同节点,判断是否是相同节点的条件是,key和sel(选择器)必须都相同(那有的人可能会说了,那我标签没有key怎么办啊,没有key那就是undefined,undefined
    === undefined 始终为true,所以没有key只需要保证sel相同就行)。如果不相同,那么执行替换操作(即新增新vnode上的元素,删除旧vnode上的元素
    例如,原来是div,新vnode变成了p,那么就是新增p元素,再删除div元素。相当于就是p替换了div),这一步,只有比较根节点时,是在patch函数中进行的。非根节点都是在updateChildren函数中执行的,因为根节点只会有一个,可以直接比较,而其他节点会存在多个,需要通过一些算法来判断,具体详情后面会说

  • 如果节点相同,那么进去第二部分,即比较两个节点的属性是否相同,节点是否存在文本,文本是否相同。是否存在子节点,子节点是否相同。这部分主要在patchVnode中执行
    那么,在第二部分,会做哪些事情呢。 1、如果存在文本时,更新文本 2、如果存在属性时,更新属性 3、如果存在子节点时,更新子节点
    那么,如何更新呢,逻辑也很简单,遵循以下规则: 1、如果旧vnode上存在,而新vnode上不存在,那么执行删除操作
    2、如果旧vnode上不存在,而新vnode上存在,那么执行新增操作
    3、如果新旧vnode上都存在,那么执行替换操作(即,新增新的,删除旧的),文本,和属性的替换是在这部分完成。而对于子节点,如果新vnode和旧vnode上都存在子节点时,那么会进入第三部分比较。比较子节点的差异。

  • 第三部分,主要在updateChildren函数中执行,主要用于比较某个节点下的子节点差异

  • 我们在最后整理一下步骤。

  • 1 (patch函数)、比较两个虚拟dom树,对根节点root进行执行patch(oldVnode,newVnode)函数,比较两个根节点是否是相同节点。如果不同,直接替换(新增新的,删除旧的)

  • 2 (patchVnode函数)、如果相同,对两个节点执行patchVnode(oldVnode, newVnode),比较属性,文本,已经子节点。此时,要么新增,要么删除。要么直接修改文本内容。只有当都存在子节点时,并且oldVnode === newVnode 为false时。会执行updateChildren函数,去进一步比较他们的子节点。

  • 3、比较分3大类。

  • 第一类:oldStart === newStart, oldStart === newEnd,oldEnd === newStart,oldEnd === newEnd
    这4种情况的比较。如果这4种情况中任何一种匹配。那么会执行patchVnode进一步比较。同时指针往中间移

  • 第二类:oldStart > oldEnd 或者 newStart >newEnd时。表示匹配结束。此时,多余的元素删除,新增的元素新增。

  • 第三类:上面几种情况都不匹配。那么这个时候key是否存在。就起到关键性作用了。存在key时,可以直接通过key去找到节点的原来的位置。如果没有找到,就新增节点。找到了,就移动节点位置。查找效率非常高
    而如果没有key呢,那么压根就不会去原来的节点中查找了。而是直接新增这个节点。这就导致这个节点下的所有子节点都会被重新新增。会出现明显的性能损耗。所以,合理的应用key,也是一种性能上的优化。

简而言之diff算法的过程就是patch函数——>patchVnode函数——>updateChildren函数——>patchVnode函数——>updateChildren函数…循环递归的过程

你可能感兴趣的:(JS,r语言,算法,vue.js)