vue3 源码解析(7)— diff 算法源码的实现

前言

vue3 采用的 diff 算法名为快速 diff 算法,整个 diff 的过程分为以下5个阶段完成。

  1. 处理前置节点
  2. 处理后置节点
  3. 处理仅有新增节点
  4. 处理仅有删除节点
  5. 处理其他情况(新增 / 卸载 / 移动)

这里我们先定义新旧两个节点列表,接下来我们通过这两个列表来模拟下 vue3 的 diff 算法的整个过程。

在这里插入图片描述

处理前置节点

从前到后对比两个节点,节点相同则 patch 打补丁更新

在这里插入图片描述
这里我们先定义一个 i 变量用于记录前置索引值并初始化 i = 0,逐个比较它们的节点是否相同。在遍历中可知当 i = 2 的时候新旧节点的值不一样那么我们就停在这里并记录当前 i = 2。经过这一步我们已经处理完成了前面两个节点。对应的 vue3 源码在这里:

let i = 0
const l2 = c2.length
let e1 = c1.length - 1 // prev ending index
let e2 = l2 - 1 // next ending index// 1. sync from start
while (i <= e1 && i <= e2) {const n1 = c1[i]const n2 = c2[i]if (isSameVNodeType(n1, n2)) {patch(n1, n2,container)} else {break}i++
}

处理后置节点

从后往前对比两个节点,节点相同则 patch 打补丁更新

在这里插入图片描述

这里我们定义一个 e1 e2 分别记录新旧节点列表的后置索引值。在遍历中可知当 e1 = 5 e2 = 5 的时候新旧节点的值不一样那么我们就停在这里并记录当前 e1e2。经过这一步我们已经处理完成了后面一个节点。对应的 vue3 源码在这里:

// 2. sync from end
while (i <= e1 && i <= e2) {const n1 = c1[e1]const n2 = c2[e2]if (isSameVNodeType(n1, n2)) {patch(n1, n2,container)} else {break}e1--e2--
}

处理仅有新增节点

新节点还没遍历完而旧节点已遍历结束

在这里插入图片描述
在遍历过程中会出现 i > e1 && i <= e2 的情况(旧的少 新的多),这种情况代表有节点需要被新增。对应的 vue3 源码在这里:

if (i > e1) {if (i <= e2) {const nextPos = e2 + 1 // 插入的位置const anchor = nextPos < l2 ? c2[nextPos].el : parentAnchor // 参照物while (i <= e2) {patch(null, c2[i],container, anchor)i++}}
}

处理仅有删除节点

新节点已遍历结束而旧节点还没遍历完

在这里插入图片描述
在遍历过程中同样也会出现 i > e2 的情况(旧的多 新的少),这种情况代表有节点需要被删除。对应的 vue3 源码在这里:

if (i > e2) {while (i <= e1) {unmount(c1[i], parentComponent, parentSuspense, true)i++}
}

处理其他情况(新增 / 卸载 / 移动)

在完成前置后置预处理后,最后我们来处理有新增、卸载、移动的复杂情况。让我们来看下 vue3 是如何完成这种复杂情况的操作。我们先定义如下几个变量:

  1. s1 s2 分别记录新旧节点列表要处理部分的起始位置。
  2. keyToNewIndexMap 用于保存新节点与位置的索引关系。
  3. moved = false 表示移动标识。
  4. maxNewIndexSoFar = 0 用于记录新节点中当前的最远位置,目的是用于判断新旧节点在遍历过程中是否呈现递增趋势,如果不是则证明节点产生了移动,需要设置 moved = true 后续进行移动处理。
  5. newIndexToOldIndexMap 用于记录新旧节点位置的映射关系,初始值都为 0,如果处理完之后还是保持 0 这个值的话则判定为新节点后续需要挂载。

在这里插入图片描述

const s1 = i // prev starting index
const s2 = i // next starting index// 5.1 build key:index map for newChildren
const keyToNewIndexMap = new Map()
for (i = s2; i <= e2; i++) {const nextChild = c2[i]if (nextChild.key != null) {keyToNewIndexMap.set(nextChild.key, i)}
}// 5.2 loop through old children left to be patched and try to patch
// matching nodes & remove nodes that are no longer present
let j
let patched = 0
const toBePatched = e2 - s2 + 1
let moved = false
// used to track whether any node has moved
let maxNewIndexSoFar = 0
// works as Map<newIndex, oldIndex>
// Note that oldIndex is offset by +1
// and oldIndex = 0 is a special value indicating the new node has
// no corresponding old node.
// used for determining longest stable subsequence
const newIndexToOldIndexMap = new Array(toBePatched)
for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0

接下来我们从旧节点列表中依次取值并判断获取到的值是否存在于新节点列表中其结果只会包含两种情况:存在和不存在。

  • 不存在:直接卸载该节点。
  • 存在:
    1. 更新 newIndexToOldIndexMap 对应下标的值。
    2. 对比新节点位置索引值和当前最远位置如果 newIndex >= maxNewIndexSoFar 则更新 maxNewIndexSoFar = newIndex 的值,否则的话更新 moved = true

在这里插入图片描述
对应的 vue3 源码在这里:

for (i = s1; i <= e1; i++) {const prevChild = c1[i]if (patched >= toBePatched) {// all new children have been patched so this can only be a removalunmount(prevChild, parentComponent, parentSuspense, true)continue}let newIndexif (prevChild.key != null) {newIndex = keyToNewIndexMap.get(prevChild.key)} else {// key-less node, try to locate a key-less node of the same typefor (j = s2; j <= e2; j++) {if (newIndexToOldIndexMap[j - s2] === 0 &&isSameVNodeType(prevChild, c2[j] as VNode)) {newIndex = jbreak}}}if (newIndex === undefined) {unmount(prevChild, parentComponent, parentSuspense, true)} else {newIndexToOldIndexMap[newIndex - s2] = i + 1if (newIndex >= maxNewIndexSoFar) {maxNewIndexSoFar = newIndex} else {moved = true}patch(prevChild, c2, container,null)patched++}
}

需要注意的是仅当节点移动时才生成最长递增子序列,其目的是让节点可以做到最小限度的移动操作

在这里插入图片描述

接下来从后开始往前遍历处理新旧节点位置映射表:

  • i = 3 对应的位置值为 0 表示该节点为新节点后续需要挂载。
  • i = 2 处于最长递增子序列 j = 1 的地方,直接跳过,同时 ij 需要同时往上移动。
  • i = 1 处于最长递增子序列 j = 0 的地方,直接跳过,同时 ij 需要同时往上移动。
  • i = 0 对应的位置值为 6,i = 0 不处于最长递增子序列中因此该节点需要移动。

整个过程只是新挂载了 n8 节点、卸载了 n3 节点、移动了 n6 节点,其他均为原地 patch 更新,这样的处理使性能得到了极大的提升。

在这里插入图片描述
对应的 vue3 源码在这里:

// 5.3 move and mount
// generate longest stable subsequence only when nodes have moved
const increasingNewIndexSequence = moved? getSequence(newIndexToOldIndexMap): EMPTY_ARR
j = increasingNewIndexSequence.length - 1// looping backwards so that we can use last patched node as anchor
for (i = toBePatched - 1; i >= 0; i--) {const nextIndex = s2 + iconst nextChild = c2[nextIndex]const anchor = nextIndex + 1 < l2 ? c2[nextIndex + 1.el : parentAnchorif (newIndexToOldIndexMap[i] === 0) {// mount newpatch(null, nextChild, container, anchor)} else if (moved) {// move if:// There is no stable subsequence (e.g. a reverse)// OR current node is not among the stable sequenceif (j < 0 || i !== increasingNewIndexSequence[j]) {move(nextChild, container, anchor, MoveType.REORDER)} else {j--}}
}

由于 vue3 diff 算法源码内容较多这里就不在贴出来,最后附上源码链接感兴趣的小伙伴可以深入的了解下。

总结

以上就是 vue3 diff 算法的大致流程,本篇文章为 【前端面试】Vue3 DOM Diff】的文字记录版。如有不明白或者是写错的地方还希望大家可以指出来,最后码字不易,希望大家可以素质三连。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/822573.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据结构速成--栈

由于是速成专题&#xff0c;因此内容不会十分全面&#xff0c;只会涵盖考试重点&#xff0c;各学校课程要求不同 &#xff0c;大家可以按照考纲复习&#xff0c;不全面的内容&#xff0c;可以看一下小编主页数据结构初阶的内容&#xff0c;找到对应专题详细学习一下。 目录 一…

JavaWeb--前端--03Vue入门

Vue入门 1 Vue概述2 快速入门3 Vue指令3.1 v-bind和v-model3.2 v-on3.3 v-if和v-show3.4 v-for3.5 案例 4 生命周期 1 Vue概述 个完整的html页面包括了视图和数据&#xff0c;数据是通过请求 从后台获取的&#xff0c;那么意味着我们需要将后台获取到的数据呈现到页面上&#…

OSPF - 链路状态路由协议

IGP 外部网关路由协议&#xff1a; OSPF &#xff0c; IS-IS EGP 内部网关路由协议&#xff1a; BGP 协议算法&#xff1a; 距离矢量路由协议 链路状态路由协议 lsdb:链路状态数据库 - 存放lsa的地址 RIP&#xff1a;有方向的矢量&#xff0c;距离矢量路由协议&#xf…

PHP-extract变量覆盖

[题目信息]&#xff1a; 题目名称题目难度PHP-extract变量覆盖1 [题目考点]&#xff1a; 变量覆盖指的是用我们自定义的参数值替换程序原有的变量值&#xff0c;一般变量覆盖漏洞需要结合程序的其它功能来实现完整的攻击。 经常导致变量覆盖漏洞场景有&#xff1a;$$&#x…

最前沿・量子退火建模方法(2) : Domain wall encoding讲解和python实现

前言 上篇讲的subQUBO属于方法论&#xff0c;这次讲个通过编码量子比特的方式&#xff0c;同样的约束条件&#xff0c;不同的编码&#xff0c;所需的量子比特数是不同的。有的编码方式&#xff0c;很节省量子比特。比如&#xff0c;这次要讲的Domain wall encoding。 一、Doma…

Vue.js前端开发零基础教学(六)

学习目标 了解什么是路由&#xff0c;能够说出前端后端路由的原理 掌握多种路由的使用方法&#xff0c;能够实现路由的不同功能 掌握Vue Router的安装及基本使用方法 5.1 初始路由 提到路由&#xff08;Route),一般我们会联想到网络中常见的路由器&#xff08;Router),…

CSS3 max/min-content及fit-content、fill-available值的详解

c3中对width的值多了几个值&#xff1a;fill-available, max-content, min-content, 以及fit-content。 1.width:fill-available 我们在页面中扔一个没有其他样式的<div>元素&#xff0c;则&#xff0c;此时&#xff0c;该<div>元素的width表现就是fill-availabl…

杰理-701-更换字库

杰里-701-更换字库显示 工具&#xff1a;链接&#xff1a;https://pan.baidu.com/s/1yMDatiRCaJj2ioKXF-H8GQ 把使用的字库文件放进该目录下 生成后的字库文件需要修改名称 把修改好名称的字库文件放到该目录下替换 代码,把所有语言的PIX修改未新替换的字库文件&#xff08;保…

00_Qt概述以及如何创建一个QT新项目

Qt概述 1.Qt概述1.1 什么是Qt1.2 Qt的发展史1.3 支持的平台1.4 Qt版本1.5 Qt的下载与安装1.6 Qt的优点 2.QT新项目创建3.pro文件4.主函数5.代码命名规范和快捷键 1.Qt概述 1.1 什么是Qt Qt是一个跨平台的C图形用户界面应用程序框架。它为应用程序开发者提供建立艺术级图形界面…

机器人视觉软件实现目标检测通常借助深度学习技术和计算机视觉算法

机器人视觉软件实现目标检测通常借助深度学习技术和计算机视觉算法。以下是一般而言的目标检测实现步骤&#xff1a; 1、数据收集与标注&#xff1a;首先需要收集包含目标物体的大量图像数据&#xff0c;并对这些图像进行标注&#xff0c;标注出目标物体的位置和类别信息。这些…

一篇安装配置ubuntu22.04(步骤详细,配置成功)

一篇配置ubuntu22.04(步骤详细&#xff0c;配置成功) 官网下载相应的镜像 vitualbox安装ubuntu 新建虚拟机 第一步 第二步 第三步、按需分配内存、处理器个数、磁盘大小 第四步、一直下一步直至完成 配置虚拟机网络 第一步、先停止虚拟机 第二步、设置虚拟机网络 正常启…

【C++】一篇文章带你深入了解vector

目录 一、vector的介绍二、 标准库中的vector2.1 vector的常见接口说明2.1.1 vector对象的常见构造2.1.1.1 [无参构造函数](https://legacy.cplusplus.com/reference/vector/vector/vector/)2.1.1.2 [有参构造函数&#xff08;构造并初始化n个val&#xff09;](https://legacy.…

Advanced RAG 03:运用 RAGAs 与 LlamaIndex 评估 RAG 应用

编者按&#xff1a;目前&#xff0c;检索增强生成&#xff08;Retrieval Augmented Generation&#xff0c;RAG&#xff09;技术已经广泛使用于各种大模型应用场景。然而&#xff0c;如何准确评估 RAG 系统的性能和效果&#xff0c;一直是业界和学界共同关注的重点问题。若无法…

设计模式之状态模式(下)

3&#xff09;共享状态 1.概述 在某些情况下&#xff0c;多个环境对象可能需要共享同一个状态&#xff0c;如果希望在系统中实现多个环境对象共享一个或多个状态对象&#xff0c;那么需要将这些状态对象定义为环境类的静态成员对象。 2.案例 背景&#xff1a;要求两个开关对…

前端-vue项目debugger调试

一、前言 有的时候接受同事一个项目&#xff0c;用框架不一样&#xff0c;写的也不太规范&#xff0c;那么就需要打断点去学习改项目的流程了。 那么vue项目是如何debugger调试呢&#xff1f; 二、操作 大概理解一下&#xff0c;vue项目启动&#xff0c;大概是先启动框架&am…

高效进行文件夹批量改名,轻松实现英文到中文的翻译,让你的文件夹管理更高效!

在数字化时代&#xff0c;我们每天都在与无数的文件夹打交道。而管理这些文件夹&#xff0c;尤其是为它们命名&#xff0c;往往成为一项繁琐而耗时的任务。尤其是当文件夹名以英文命名时&#xff0c;对于非英语用户来说&#xff0c;理解和记忆都可能会成为一道难题。那么如何翻…

HarmonyOS4-数据持久化

轻量级preferences&#xff1a; 关系型数据库&#xff1a; 增删改&#xff1a; 查询语句&#xff1a; 具体详情代码可参与源码&#xff1a; 黑马大佬写的。 harmonyos-lessons: 黑马程序员B站HarmonyOS课程的基础篇代码部分

C语言学习/复习20

一、调试 1.实例1&#xff1a; 经调试&#xff0c;该代码因数组越界会死循环 二、优秀的代码 注意事项&#xff1a;assert()返回真假并决定是否报错 常量指针本质是指针&#xff0c;常量修饰它&#xff0c;表示这个指针是一个指向常量的指针&#xff08;变量&#xff09…

MySQL 基础使用

文章目录 一、Navicat 工具链接 Mysql二、数据库的使用1.常用数据类型2. 建表 create3. 删表 drop4. insert 插入数据5. select 查询数据6. update 修改数据7. delete 删除记录truncate table 删除数据 三、字段约束字段1. 主键 自增delete和truncate自增长字段的影响 2. 非空…