从:key的角度,来看Vue3中diff算法的实现原理(多图详解)

简介: 多图详解diff算法
Hi~,我是 一碗周,一个在舒适区垂死挣扎的前端,如果写的文章有幸可以得到你的青睐,万分有幸~

写在前面

在我们使用v-for指令的时候,尤大大建议我们为每一项都添加一个唯一的属性key,在Vue文档中是这么说的:

为了给Vue一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一的keyattribute,除非遍历输出的 DOM 内容非常简单。

看到这,是不是会有以下疑问:

  • 为什么要添加一个唯一的key
  • 为什么不建议用索引作为唯一的key
  • 为什么不能使用随机数作为唯一的key

接下来我们依次回答这些问题。

key是什么

在文档中对key属性,解释是这样的:

  • key属性主要用在Vue的虚拟DOM算法提示,在对比新旧nodes时辨识VNodes;
  • 如果不使用key,Vue会使用一种算法最小化元素的移动并且尽可能的尝试就地修改/复用相同类型元素;
  • 而使用key时,它会基于key的变化重新排列元素顺序,并且会移除/销毁key不存在的元素。

看了这些内容就更加的懵了,又出现了新的问题:

  • 什么是Vnode?
  • 什么是虚拟DOM?
  • 没有key的时候,如何尝试修改和复用的?
  • key的时候,如何基于key重写排列?

我们接着看。

虚拟DOM

VNode

首先我们先来看一下VNode。VNode即Virtual Node,也就是虚拟节点,在Vue中无论是组件还是元素,最终表示出来的都是一个个VNode,VNode的本质其实就是一个JavaScript对象,例如下面这个节点:

<div class="name" style="font-size: 3rem; color: #333;">一碗周</div>

它最终的VNode是这样的:

const vNode = {
  type: 'div',
  props: {
    class: 'name',
    style: {
      fontSize: '3rem',
      color: '#333',
    },
  },
  children: '一碗周',
}

虚拟DOM

如果不是只有一个节点,而是有很多个节点,形成了一个VNode Tree,也就是虚拟DOM,例如下面这张图:

01_虚拟DOM.png

也就是说在Vue中写HTML代码,先会被转换为虚拟DOM,然后才会被渲染成真实DOM。

虚拟DOM VS 真实DOM

前面我们介绍了,Vue中渲染真实DOM一共需要三个步骤,而原生仅仅需要两个步骤,如下图:

02_DOM渲染过程.png

如果单从图上看的话,毫无悬念,直接生成真实DOM的方式更快一些。

但实际上,如果某个节点的内容发生变化时,比较虚拟DOM比直接比较真实DOM在性能上是要更优的,这也是为什么Vue、React等框架采用虚拟DOM的原因。

上面所说的比较,就是采用的diff算法。

不使用key属性的处理方式

现在我们来看一下不使用key属性是何如处理的,我们找到相关的源码,在packages\runtime-core\src\renderer.ts中,大概1600行的位置,这里面表明了存在key和不存在key分别调用哪个函数,如下图:

03_有key和没key的处理方式.png

现在我们直接定位到patchUnkeyedChildren()方法,看看在这个方法中做了些什么,如下图:

04_patchUnkeyedChildren解析.png

如果只看图不理解的话,咱们举一个例子,代码如下:

<body>
  <div id="app"></div>
  <template id="my-app">
    <ul class="list-group">
      <li class="list-group-item" v-for="item in list">{{item.name}}</li>
    </ul>
  </template>
  <script src="https://unpkg.com/vue@next"></script>
  <script>
    Vue.createApp({
      template: '#my-app',
      data() {
        return {
          list: [
            { id: 0, name: 'HTML' },
            { id: 1, name: 'CSS' },
            { id: 2, name: 'JavaScript' },
            { id: 3, name: 'Vue.js' },
            { id: 4, name: 'React.js' },
          ],
        }
      },
    }).mount('#app')
  </script>
</body>

运行结果如下:

05_demo1.png

现在我们在Vue.js前面插入一个{id: 5, name: 'Ajax'},其新旧虚拟DOM如下图所示:

06_demo1解析1.png

执行过程如下:

06_demo1解析2.png

如果old VDOMnew VDOM多的化,多余的部分直接卸载。

使用key属性的处理

使用key属性的处理方式是这篇文章的重头戏,我们首先看一下patchKeyedChildren()方法做了些什么,如下图:

07_patchKeyedChildren函数.png

我们可以看到,这个方法比较长,但是它分为5个步骤,我们依次来看:

tips:开发Vue框架的大佬都写注释,你还觉着不需要写注释吗????

我们依次来看,第一步,从头开始遍历,主要内容如下图:

07_patchKeyedChildren函数01.png

我们举个例子:

08_demo2解析01.png

首先key0的比较,发现一致,继续比较,直到key3key5的比较,不一致直接跳出这个while循环。

第二步的操作与第一步基本一致,只不过就是从尾部开始,主要内容如下:

09_patchKeyedChildren函数02.png

例子:

10_demo2解析.png

第三步和第四步,分别是添加新节点或者移除旧节点,主要代码如下:

11_patchKeyedChildren函数03.png

例子:

12_demo3解析.png

最后一步,也就是最复杂的异常,这个是用于处理中间的乱序,或者移动新增等问题,例子如下:

13_demo4解析.png

所以我们可以发现,Vue在进行diff算法的时候,会尽量利用key来优化操作,所以说key属性是必须的。

为什么不能使用索引作为key

如果我们是使用index作为key,假如有下面这个数组:

list: [
  {name: 'HTML'},
  {name: 'CSS'},
  {name: 'JS'},
]

我们在最前面添加一项:

list.unshift({name: '一碗周'})

如果使用索引做key的话,会出现下面这种问题:

14_使用index作为key.png

所以说我们尽量要使用唯一的且不变的值作为key

注意:千万不要使用随机数作为key,不然每次比较都不相同

写在最后

以上就是这篇文章的全部内容,如果那里有错误,欢迎指正;如果帮助到你,可以帮我点个赞支持一下,毕竟画图不易~

目录
相关文章
|
24天前
|
算法 JavaScript UED
Diff 算法的实现原理
【10月更文挑战第18天】Diff 算法是 Vue.js 中实现高效 DOM 更新的核心机制,通过合理的比较和优化策略,能够在保证界面正确性的同时,最大程度地减少 DOM 操作,提高应用的性能和用户体验。
26 2
|
24天前
|
算法 JavaScript
Vue 中的 Diff 算法
【10月更文挑战第18天】需要注意的是,Diff 算法虽然能够提高性能,但在某些复杂的场景下,可能仍然会存在一些性能瓶颈。因此,在实际开发中,我们需要根据具体情况合理地使用 Diff 算法,并结合其他优化手段来提高应用的性能。
12 1
|
1月前
|
JavaScript 算法 前端开发
vue 中diff算法
【10月更文挑战第10天】
28 1
|
1月前
|
JavaScript 算法 前端开发
【VUE】Vue的diff算法和React的diff算法
【VUE】Vue的diff算法和React的diff算法
|
22天前
|
算法 Java
介绍一下CAS算法的实现原理
【10月更文挑战第20天】介绍一下CAS算法的实现原理
10 0
|
6月前
|
JavaScript API
【vue实战项目】通用管理系统:api封装、404页
【vue实战项目】通用管理系统:api封装、404页
76 3
|
6月前
|
人工智能 JavaScript 前端开发
毕设项目-基于Springboot和Vue实现蛋糕商城系统(三)
毕设项目-基于Springboot和Vue实现蛋糕商城系统
|
6月前
|
JavaScript Java 关系型数据库
毕设项目-基于Springboot和Vue实现蛋糕商城系统(一)
毕设项目-基于Springboot和Vue实现蛋糕商城系统
174 0
|
6月前
|
JavaScript 前端开发 API
Vue3+Vite+TypeScript常用项目模块详解
现在无论gitee还是github,越来越多的前端开源项目采用Vue3+Vite+TypeScript+Pinia+Elementplus+axios+Sass(css预编译语言等),其中还有各种项目配置比如eslint 校验代码工具配置等等,而我们想要进行前端项目的二次开发,就必须了解会使用这些东西,所以作者写了这篇文章进行简单的介绍。
142 0
Vue3+Vite+TypeScript常用项目模块详解
|
6月前
|
设计模式 JavaScript
探索 Vue Mixin 的世界:如何轻松复用代码并提高项目性能(上)
探索 Vue Mixin 的世界:如何轻松复用代码并提高项目性能(上)
探索 Vue Mixin 的世界:如何轻松复用代码并提高项目性能(上)