前言
在我们使用vue的v-for去循环一个列表的时候,要求去填写一个key,都说写了key性能会更好,那这究竟是为啥呢,本文就带大家去了解这里面的真相
分析
我们先看第一段代码
<script setup>
import { ref } from "vue";
const list = ref([1, 2, 3]);
const change = () => {
list.value.reverse();
};
</script>
<template>
<ul>
<li v-for="(item, index) in list">{{ item }}</li>
</ul>
<button @click="change">修改</button>
</template>
这段代码很简单就是一个列表加一个按钮,按钮的效果就是反转列表
接下来我们去观察dom的渲染
可以看到,第一个和第三个重新的渲染了,但是第二个没有重新渲染
这是由于我们没有给li设置key,那么默认vue3会给他去使用index做key,也就是说,现在三个li的key分别是0、1、2,我们将数组的反转以后,那么原来key是0的li现在内容就换成2的,原来key2的li的内容就换为了0,其中2的key不变,也就可以复用了
接下来我们修改数据,将他们都设置唯一的id,并将id作为key
<script setup>
import { ref } from "vue";
const list = ref([
{
id: 1,
data: 1,
},
{
id: 2,
data: 2,
},
{
id: 3,
data: 3,
},
]);
const change = () => {
// list根据data排序
list.value.sort((a, b) => a.data - b.data);
};
</script>
<template>
<ul>
<li v-for="(item, index) in list" :key="item.id">{{ item.data }}</li>
</ul>
<button @click="change">修改</button>
</template>
这样就可以得到复用,因为每个数据的key没有变,所以vue会将他能复用
虚拟dom
虚拟DOM(Virtual DOM)是一种用于提高Web应用性能的技术。它的核心思想是:
-
在内存中保持一个与真实DOM结构完全一致的虚拟DOM树。
-
当数据发生变化时,根据新的数据重新构建一棵新的虚拟DOM树。
-
然后将新旧两棵虚拟DOM树进行对比,找出差异。
-
最后只更新实际发生变化的DOM节点,而不是重新渲染整个页面。虚拟DOM(Virtual DOM)是一种用于提高Web应用性能的技术。它的核心思想是:
-
在内存中保持一个与真实DOM结构完全一致的虚拟DOM树。
-
当数据发生变化时,根据新的数据重新构建一棵新的虚拟DOM树。
-
然后将新旧两棵虚拟DOM树进行对比,找出差异。
-
最后只更新实际发生变化的DOM节点,而不是重新渲染整个页面。
Diff算法
Diff (Difference)是虚拟DOM中一项非常关键的技术,它用于比较新旧两棵虚拟DOM树之间的差异。
流程是
-
同层比较
- Diff 算法会首先比较新旧两棵虚拟 DOM 树的同一层级节点。
- 比较的原则是:tag 类型相同且 key 值相同的节点被认为是相同节点。
-
分类处理
-
基于上一步的比较结果, Diff 算法会将节点分为以下 3 类:
- 新增节点
- 删除节点
- 移动/更新节点
-
-
新增节点
- 如果在新的虚拟 DOM 树中发现了在旧的虚拟 DOM 树中不存在的节点,则将其标记为新增节点。
- 对于新增节点,Diff 算法会在真实 DOM 中创建并插入这个新节点。
-
删除节点
- 如果在旧的虚拟 DOM 树中发现了在新的虚拟 DOM 树中不存在的节点,则将其标记为删除节点。
- 对于删除节点,Diff 算法会在真实 DOM 中移除这个节点。
-
移动/更新节点
- 如果节点的 tag 类型相同且 key 值相同,则认为是同一个节点,需要进行移动或更新操作。
- Diff 算法会比较该节点的属性,如果有变化则更新真实 DOM 上对应的属性。
- 同时 Diff 算法还会识别出节点顺序的变化,对这些节点进行移动操作,减少不必要的 DOM 操作。
-
递归比较
- Diff 算法会递归地比较新旧虚拟 DOM 树的子节点,重复上述步骤,直到所有节点都比较完毕。
并且阅读源码我们可以看到
function sameVnode (a, b) {
return (
a.key === b.key && (
(
a.tag === b.tag &&
a.isComment === b.isComment &&
isDef(a.data) === isDef(b.data) &&
sameInputType(a, b)
) || (
isTrue(a.isAsyncPlaceholder) &&
a.asyncFactory === b.asyncFactory &&
isUndef(b.asyncFactory.error)
)
)
)
}
在对比两个节点是否相同首先对比的就key,。也就是说key不同的dom就直接认为是不同的dom,只有key相同了才有下一步比较的必要,这也就是为什么key如此的重要了
提问
- 可以使用index作为key吗?
不建议,在diff算法中判断两个节点是否相同,首先判断的就是两个节点的key是否相同,如果用index作为key,index不会随着元素位置的变更而移动,从而导致相同可复用的节点被认为不相同,降低了dom的复用性
- 可以使用随机数作为key吗?
不可以,使用了随机数,dom将毫无复用可言
- 虚拟dom优点
- 跨平台
- 跨平台是其最大的优点,将dom虚拟成对象,这样具有通用性,这样就可以根据虚拟dom去生成其他平台的代码
- 分摊了浏览器渲染线程的性能开销,减少了回流重绘
- vue不会动一次虚拟dom就渲染页面一次,而是在一段时间的操作之后,再去生成对应的真实dom
- 开销很多的v8算力
- 虚拟dom虽然让浏览器的渲染减少了回流和重绘的次数,但是这里的页面的变化都是通过虚拟dom完成的,需要js引擎去做计算
- 跨平台