前端学习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 树的逐层比较

  1. 对比节点类型:

如果新旧节点类型不同,直接替换旧节点。

  1. 更新节点属性:

比较新旧节点的属性(props),执行相应的新增、删除或修改操作。

  1. 对子节点递归比较:

如果新旧节点都有子节点,递归调用 Diff 算法,处理子节点的变化。

3.2同层节点的更新

双端比较算法

Vue 使用 双端比较 处理同层的子节点,主要逻辑如下:

  1. 设置两个指针,分别指向新旧子节点的头部和尾部。
  2. 比较头尾指针所指的节点:
  • 如果相同,则复用节点,移动指针。
  • 如果不同,则检查中间部分的节点,找到可复用节点或新增、删除节点。
  1. 当头尾指针相遇时,处理剩余的新增或删除节点。

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在实际中的使用

  1. 模板渲染中的更新优化

vue 的核心渲染流程是通过虚拟 DOM 计算视图的变化,然后将差异反映到真实 DOM 中。

  1. 组件更新与复用

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 的新节点。

#前端学习#
全部评论

相关推荐

评论
7
12
分享

创作者周榜

更多
牛客网
牛客企业服务