前端学习10 虚拟DOM和DIff算法
1. 虚拟DOM
虚拟dom是当前前端最流行的两个框架(vue和react)都用到的一种技术,都说他能帮助vue和react提升渲染性能,提升用户体验。
虚拟DOM就是一个普通的js对象(VNode 节点),是一个用来描述真实dom结构的js对象。
为什么要用虚拟DOM
你用传统的原生api或jQuery去操作DOM时,浏览器会从构建DOM树开始从头到尾执行一遍流程。
当你在一次操作时,需要更新10个DOM节点,浏览器没这么智能,收到第一个更新DOM请求后,并不知道后续还有9次更新操作,因此会马上执行流程,最终执行10次流程。
而通过VNode,同样更新10个DOM节点,虚拟DOM不会立即操作DOM,而是将这10次更新的diff内容保存到本地的一个js对象中,最终将这个js对象一次性attach到DOM树上,避免大量的无谓计算。
所以虚拟DOM出现的主要目的就是为了减少频繁操作DOM而引起回流重绘所引发的性能问题的!
vue 虚拟DOM的几个核心方法(snabbdom库来实现虚拟DOM,vue以前用的是virtual-dom,从2.x版本后使用的是snabbdom)
-
h函数
-
patch函数
-
patchVnode函数
-
updateChildren函数
而diff算法就是通过这几个函数实现的。
2. diff 算法
diff算法就是用于比较新旧两个虚拟dom之间差异的一种算法。
Vue的diff算法基于以下假设进行优化:
1、层级稳定性:
节点通常不会跨层级移动。因此,Diff 算法只需比较同一层级的节点,而不需要递归比较整个树。
2、key 的重要性
使用 key 可以唯一标识节点,帮助算法直接找到可复用的节点,避免大量顺序比较。
3、动态节点是少数
Vue 的模板编译器会将大部分内容标记为静态节点,仅对动态节点进行 Diff,从而减少计算量。
基于这些假设,Vue 的 Diff 算法在实现中采用了 双端比较 和 局部优化 的策略,大幅提升了性能。
3. Vue Diff 流程
Vue 的 Diff 算法分为两部分:
树的逐层比较(深度优先递归):
Vue 从根节点开始,逐层比较新旧虚拟 DOM 树的差异,按层处理更新操作。
同层节点的更新:
Vue 在每层中使用 双端比较算法 和 key 快速定位节点,处理新增、删除、复用和移动的场景。
3.1 树的逐层比较
- 对比节点类型:
如果新旧节点类型不同,直接替换旧节点。
- 更新节点属性:
比较新旧节点的属性(props),执行相应的新增、删除或修改操作。
- 对子节点递归比较:
如果新旧节点都有子节点,递归调用 Diff 算法,处理子节点的变化。
3.2同层节点的更新
双端比较算法
Vue 使用 双端比较 处理同层的子节点,主要逻辑如下:
- 设置两个指针,分别指向新旧子节点的头部和尾部。
- 比较头尾指针所指的节点:
- 如果相同,则复用节点,移动指针。
- 如果不同,则检查中间部分的节点,找到可复用节点或新增、删除节点。
- 当头尾指针相遇时,处理剩余的新增或删除节点。
key 快速定位
当子节点带有唯一的 key 时,Vue 可通过哈希表快速定位节点,进一步优化性能:
- 如果新旧子节点的 key 相同,则直接复用该节点。
- 如果没有 key,算法会退化为简单的顺序比较。
4. Vue3的性能优化
Vue 3 在 Diff 算法中引入了静态节点标记(Patch Flag)、Block Tree 结构等方法来进行优化。
4.1 静态节点标记(Patch Flag)
Vue 3 的编译器会为模板中的节点生成 Patch Flag,用于标记动态内容。
<div>
<p>{{ staticText }}</p>
<p :class="dynamicClass">{{ dynamicText }}</p>
</div>
编译后:
-
静态节点 staticText 被直接跳过。
-
动态节点 dynamicClass 和 dynamicText 被标记为动态,更新时只处理这些部分。
4.2 Block Tree 结构
Vue 3 的虚拟 DOM 被设计为 Block Tree,静态节点和动态节点被分组处理:
- 动态节点存储在 Block 中,直接进行局部更新。
- 静态节点在渲染时跳过,不参与 Diff。
5.Diff在实际中的使用
- 模板渲染中的更新优化
vue 的核心渲染流程是通过虚拟 DOM 计算视图的变化,然后将差异反映到真实 DOM 中。
- 组件更新与复用
Vue 的组件系统依赖于 Diff 算法判断子组件的复用与销毁。
例如:
<template>
<component :is="currentComponent" :key="currentComponent"></component>
</template>
切换 currentComponent 时:
- 如果 key 不同,则销毁旧组件,重新创建新组件。
- 如果 key 相同,则复用组件实例,仅更新其属性或插槽内容。
例如:
<template>
<div>
<div v-if="show">Condition A</div>
<div v-else>Condition B</div>
</div>
</template>
当 show 从 true 切换为 false 时,Vue 会销毁 Condition A 的节点,并挂载 Condition B 的新节点。
#前端学习#