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

news2024/11/24 17:20:43

前言

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 removal
    unmount(prevChild, parentComponent, parentSuspense, true)
    continue
  }
  let newIndex
  if (prevChild.key != null) {
    newIndex = keyToNewIndexMap.get(prevChild.key)
  } else {
    // key-less node, try to locate a key-less node of the same type
    for (j = s2; j <= e2; j++) {
      if (
        newIndexToOldIndexMap[j - s2] === 0 &&
        isSameVNodeType(prevChild, c2[j] as VNode)
      ) {
        newIndex = j
        break
      }
    }
  }
  if (newIndex === undefined) {
    unmount(prevChild, parentComponent, parentSuspense, true)
  } else {
    newIndexToOldIndexMap[newIndex - s2] = i + 1
    if (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 + i
  const nextChild = c2[nextIndex]
  const anchor = nextIndex + 1 < l2 ? c2[nextIndex + 1.el : parentAnchor
  if (newIndexToOldIndexMap[i] === 0) {
   	// mount new
    patch(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 sequence
    if (j < 0 || i !== increasingNewIndexSequence[j]) {
       move(nextChild, container, anchor, MoveType.REORDER)
    } else {
	   j--
	}
  }
}

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

总结

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

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

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

相关文章

4款免费又好用的AI工具!很难相信!

人工智能&#xff08;AI&#xff09;已经成为我们生活和工作中不可或缺的一部分。但是&#xff0c;一提到AI工具&#xff0c;你可能首先会想到的是高昂的价格和复杂的操作。 今天&#xff0c;君君就要打破这一刻板印象&#xff0c;带你认识4个AI工具&#xff0c;它们不仅功能强…

《QT实用小工具·二十七》各种炫酷的样式表

1、概述 源码放在文章末尾 该项目实现了各种炫酷的样式表&#xff0c;如单选、多选、按钮、日历、表格、下拉框、滚轮等&#xff0c;下面是项目demo演示&#xff1a; 项目部分代码如下&#xff1a; #include "frmmain.h" #include "ui_frmmain.h" #inc…

数仓建模—数据架构

数仓—数据架构 为了在企业决策中使用数据,数据必须经过整个数据平台的各个阶段。整个过程是什么样子的,从开始到结束?原始形式的数据是如何转化为可导致商业决策的见解的?这些问题可以通过数据架构来回答。 数据架构是指记录组织所有数据资产的模型、规则和标准。它映射…

Zephyr Windows开发环境搭建

Zephyr 如果有错误或未及时更新&#xff0c;请以官网文档为主 官网&#xff1a;https://docs.zephyrproject.org/latest/develop/getting_started/index.htm 下载安装 Chocolatey 这是一个类似于在Linux系统下 yum 和 apt 那样的包管理器 官网&#xff1a;https://chocolat…

平衡车设计——硬件篇

在本文开始之前我忍不住想吐槽一个事情&#xff0c;就在前两天晚上&#xff0c;我满意地装完平衡车&#xff0c;给他取了个名字叫瓦力&#xff08;没错&#xff0c;就是机器人总动员里的瓦力&#xff09;&#xff0c;他长这个样子。 把他放到桌子上放了一夜&#xff0c;第二天早…

【vue】Pinia-2 安装Pinia,使用store

1. 安装Pinia 在项目路径下执行npm install pinia 在package.json中查看 2. 使用store 在main.js中添加 import { createPinia } from pinia const pinia createPinia()修改createApp方法 最后示例如下&#xff08;三处修改&#xff09; import { createApp } from vue //…

Jmeter接口测试:使用教程(下)

&#x1f345; 视频学习&#xff1a;文末有免费的配套视频可观看 &#x1f345; 关注公众号&#xff1a;互联网杂货铺&#xff0c;回复1 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 上一篇我给大家讲了jmeter的基本介绍跟参数化和jmeter脚…

【开发者召集】BIMBase插件开发大赛火热报名中!| python开发 | python建模 | 二次开发 | 图形引擎 | 几何引擎

&#x1f4e2; 致所有热爱创新的技术大咖们&#xff1a; 让我们回顾一下&#xff0c;在之前的推文中&#xff0c;我们收集了各种各样的BIM插件命题~ 小伙伴们的热情让小编倍感振奋~ 因为&#xff0c;每一个命题都蕴藏着解决实际问题的潜力&#xff0c;每一个也都代表着创新的可…

工业级POE交换机的ACL

工业级POE交换机通常支持访问控制列表&#xff08;Access Control List&#xff0c;ACL&#xff09;功能&#xff0c;用于实施网络安全策略。ACL可以根据源IP地址、目标IP地址、传输协议、端口号等条件来过滤和控制网络流量。 通过配置ACL&#xff0c;可以实现以下功能&#xf…

Docker 磁盘占用过多问题处理过程记录

一、问题描述 突然发现服务器磁盘使用超过95%了&#xff08;截图时2.1 和 2.2 已经执行过了&#xff09; 二、问题分析与解决 2.1&#xff0c;docker 无用镜像占用磁盘 # 使用 docker images 查看服务的镜像 docker images# 可以手动删除一些很大不用的 docker rmi ***## 也…

【C语言】字符函数和字符串函数,详解,进来就会!

&#x1f525;博客主页&#x1f525;&#xff1a;【 坊钰_CSDN博客 】 欢迎各位点赞&#x1f44d;评论✍收藏⭐ 目录 1. 字符分类函数 2. 字符转换函数 3. strlen的使⽤和模拟实现 3.1 采用指针-指针方式 3.2 采用递归方式 3.3 采用计数器方式 4. strcpy的使⽤和模拟实现 …

如何在本地创建一个贪吃蛇小游戏node.js服务并实现无公网IP远程游玩

文章目录 前言1.安装Node.js环境2.创建node.js服务3. 访问node.js 服务4.内网穿透4.1 安装配置cpolar内网穿透4.2 创建隧道映射本地端口 5.固定公网地址 正文开始前给大家推荐个网站&#xff0c;前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽…

荔枝派LicheePi 4A RISCV板子支持的好玩的AI模型

荔枝派LicheePi 4A 是基于 Lichee Module 4A 核心板的 高性能 RISC-V Linux 开发板&#xff0c;以 TH1520 为主控核心&#xff08;4xC9101.85G&#xff0c; RV64GCV&#xff0c;4TOPSint8 NPU&#xff0c; 50GFLOP GPU&#xff09;&#xff0c;板载最大 16GB 64bit LPDDR4X&…

mysql查看数据库表容量大小

【推荐】单表行数超过 500 万行或者单表容量超过 2GB&#xff0c;才推荐进行分库分表。 说明&#xff1a;如果预计三年后的数据量根本达不到这个级别&#xff0c;请不要在创建表时就分库分表。 1. 查询所有数据库记录数和容量 SELECTtable_schema AS 数据库,SUM(table_rows) …

MySQL高级(性能分析-查看执行频次、慢查询日志)

目录 1、SQL性能分析 1.1、SQL执行频率 1.2、慢查询日志 1、SQL性能分析 1.1、SQL执行频率 MySQL 客户端连接成功后&#xff0c;通过 show [ session | global ] status 命令可以提供服务器状态信息。通过如下指令&#xff0c;可以查看当前数据库的 insert、update、delete、…

物理页采样内核配置damon和perf

一、安装报错Missing file: arch/x86/boot/bzImage [sudo] password for xmu: arch/x86/Makefile:142: CONFIG_X86_X32 enabled but no binutils support sh ./arch/x86/boot/install.sh 5.15.19-htmm-test1 \arch/x86/boot/bzImage System.map "/boot"*** Missing…

电商网站建设开发

随着互联网技术的飞速发展&#xff0c;电子商务已经成为一种全新的商业模式&#xff0c;许多传统的企业也开始涉足电商领域。对于想要进行网络销售的企业来说&#xff0c;电商网站建设开发是非常重要的环节。下面将从几个方面介绍电商网站建设开发的必要性和关键点。 一、 提升…

[入门]测试原则-ApiHug准备-测试篇-006

&#x1f917; ApiHug {Postman|Swagger|Api...} 快↑ 准√ 省↓ GitHub - apihug/apihug.com: All abou the Apihug apihug.com: 有爱&#xff0c;有温度&#xff0c;有质量&#xff0c;有信任ApiHug - API design Copilot - IntelliJ IDEs Plugin | Marketplace 写在前面…

2024年机电一体化与交通运输国际学术会议(IACMIT 2024)

2024年机电一体化与交通运输国际学术会议&#xff08;IACMIT 2024) 2024 International Conference on Mechatronics Integration and Transportation 一、【会议简介】 2024年机电一体化与交通运输国际学术会议&#xff0c;是关于交通运输机械工程和机电控制技术的交流盛会。 …

neo4j使用详解(终章、neo4j的java driver使用模板及工具类——<可用于生产>)

Neo4j系列导航: neo4j安装及简单实践 cypher语法基础 cypher插入语法 cypher插入语法 cypher查询语法 cypher通用语法 cypher函数语法 neo4j索引及调优 neo4j java Driver等更多 1. 简介 本文主要是java使用neo4j driver操作neo4j的模板项目及非常有用的工具类,主要包括: 图…