vue中key的原理吗?说说你对它的理解

JavaScript022

vue中key的原理吗?说说你对它的理解,第1张

开始之前,我们先还原两个实际工作场景

那么这背后的逻辑是什么, key 的作用又是什么?

一句话来讲

当我们在使用 v-for 时,需要给单元加上 key

用 +new Date() 生成的时间戳作为 key ,手动强制触发重新渲染

举个例子:

创建一个实例,2秒后往 items 数组插入数据

在不使用 key 的情况, vue 会进行这样的操作:

[图片上传中...(image-135c49-1631019652027-0)]

分析下整体流程:

一共发生了3次更新,1次插入操作

在使用 key 的情况: vue 会进行这样的操作:

一共发生了0次更新,1次插入操作

通过上面两个小例子,可见设置 key 能够大大减少对页面的 DOM 操作,提高了 diff 效率

其实不然,文档中也明确表示

这个默认的模式是高效的,但是只适用于不依赖子组件状态或临时 DOM 状态 (例如:表单输入值) 的列表渲染输出

建议尽可能在使用 v-for 时提供 key ,除非遍历输出的 DOM 内容非常简单,或者是刻意依赖默认行为以获取性能上的提升

源码位置:core/vdom/patch.js

这里判断是否为同一个 key ,首先判断的是 key 值是否相等如果没有设置 key ,那么 key 为 undefined ,这时候 undefined 是恒等于 undefined

updateChildren 方法中会对新旧 vnode 进行 diff ,然后将比对出的结果用来更新真实的 DOM

很多同学在面试的时候都会被问到vue的虚拟DOM的diff 以及 patch 的过程,如果这vue的源码了解不是很深刻,很难通过面试官的法眼,下面就来用通俗易懂的方式聊一聊Vue的patch过程。

我们都知道Dom操作是一个特别低性能的事儿,因为每一个dom节点中都包含了各种各样的属性及方法,每一次操作dom都是这些属性及方法一遍一遍的初始化,数量一多就特别的慢。

But,

状态的改变必须要对应视图的改变,就必须要操作到Dom

So,

越少的Dom操作就能越多的提升性能

这就是虚拟Dom能够带来的最大的好处。

但是,虚拟Dom是如何做到最少的去做Dom操作呢?

其实很简单,当状态变化时,只要更新对应变化的节点,而其他没有变化的节点,则不进行操作,这样就能最大限度的减少Dom操作。

而在Vue1.0的时候,是通过细粒度绑定来进行这项优化的,因为Vue在一定程度上是知道具体哪些状态发生了变化,也知道哪些节点使用了这个节点,这样就可以通过细粒度绑定直接更新视图。

但是这样也有副作用,粒度太细的话,会有更多的内存及依赖追踪的开销,这也是特别消耗性能的事儿。

那么,改为中粒度,当状态发生变化,通知到组件界别,而用一个对象(Vnode)去描述即将渲染的节点,一一对应,这样每次更新组件节点前,先对比新旧的两个(newVnode, oldVnode),找出需要改变的节点位置,然后单独更新这些节点,就能实现上述的功能。

这个对象就是Vnode。

Vnode其实就是一个普通的js对象,里面包含了描述节点内容的属性

在Vnode中有多种不同的节点类型:

不同的节点类型只是包含的属性值不一样而已,如一个 注释类型 的节点,对应的Vnode可能就是以下:

一个 文本节点 ,可能是以下:

我们明白了Vnode是真实Dom的描述对象,那么就能通过一个算法来计算出新旧Vnode之间的差异。

这个算法,我们就称之为patching算法。

我们冷静分析,对Dom进行操作,主要是需要做以下3件事儿:

4.1 新增节点

通常在组件初次渲染时,会发生新增节点,当oldVnode不存在,而newVnode存在时,这时只需要按照Vnode的描述去渲染视图即可。

具体的渲染过程可参考下图:

4.2 删除节点

当一个节点只在oldVnode中存在,而在newVnode中不存在时,这时我们要将改节点删除。这样就完成了变更视图的工作。

在删除节点中用到了一个nodeOps的对象,里面有一个removeVnodes的函数,主要用于对节点进行删除操作。

4.3 更新节点

前面两种情况都是比较简单,也很好理解

但是最常见是是 更新节点的 情况

具体流程如下图:

其中更新子节点也是 一个递归的过程。

我们下次再讲更新子节点。