从: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,不然每次比较都不相同

写在最后

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

目录
相关文章
|
1月前
|
JavaScript 前端开发 算法
React技术栈-虚拟DOM和DOM diff算法
这篇文章介绍了React技术栈中的虚拟DOM和DOM diff算法,并通过一个实际案例展示了如何使用React组件和状态管理来实现动态更新UI。
34 2
|
2月前
|
JavaScript 前端开发 算法
react中虚拟dom和diff算法
在React中,虚拟DOM(Virtual DOM)和Diff算法是两个核心概念,它们共同工作以提高应用的性能和效率。
32 4
|
10天前
|
XML JavaScript 前端开发
学习react基础(1)_虚拟dom、diff算法、函数和class创建组件
本文介绍了React的核心概念,包括虚拟DOM、Diff算法以及如何通过函数和类创建React组件。
15 2
|
2月前
|
前端开发 算法 JavaScript
React原理之Diff算法
【8月更文挑战第24天】
|
8天前
|
机器学习/深度学习 JavaScript 算法
面试中的网红虚拟DOM,你知多少呢?深入解读diff算法
该文章深入探讨了虚拟DOM的概念及其diff算法,解释了虚拟DOM如何最小化实际DOM的更新,以此提升web应用的性能,并详细分析了diff算法的实现机制。
|
7天前
|
JavaScript
vue组件中的插槽
本文介绍了Vue中组件的插槽使用,包括单个插槽和多个具名插槽的定义及在父组件中的使用方法,展示了如何通过插槽将父组件的内容插入到子组件的指定位置。
|
5天前
|
JavaScript
vue消息订阅与发布
vue消息订阅与发布
|
2天前
|
JavaScript
理解 Vue 的 setup 应用程序钩子
【10月更文挑战第3天】`setup` 函数是 Vue 3 中的新组件选项,在组件创建前调用,作为初始化逻辑的入口。它接收 `props` 和 `context` 两个参数,内部定义的变量和函数需通过 `return` 暴露给模板。`props` 包含父组件传入的属性,`context` 包含组件上下文信息。`setup` 可替代 `beforeCreate` 和 `created` 钩子,并提供类似 `data`、`computed` 和 `methods` 的功能,支持逻辑复用和 TypeScript 类型定义。
20 11
|
6天前
|
JavaScript 前端开发 IDE
Vue学习笔记5:用Vue的事件监听 实现数据更新的实时视图显示
Vue学习笔记5:用Vue的事件监听 实现数据更新的实时视图显示
|
6天前
|
JavaScript 前端开发 API
Vue学习笔记4:用reactive() 实现数据更新的实时视图显示
Vue学习笔记4:用reactive() 实现数据更新的实时视图显示
下一篇
无影云桌面