vue3-dom-diff算法
vue3diff算法
什么是vue3diff算法
Vue3中的diff算法是一种用于比较虚拟DOM树之间差异的算法,其目的是为了高效地更新真实DOM,减少不必要的重渲染
主要过程
整个过程主要分为以下五步
- 前置预处理
- 后置预处理
- 仅处理新增
- 仅处理后置
- 处理包含新增、卸载、移动的复杂场景
第一步:前置预处理
首先定义一个变量i,记录当前索引值
定义e1、e2记录前置索引值
从前到后遍历新旧索引列表
发现它们的节点值相同,则直接patch打补丁更新
否则跳出循环进入下一步。
vue3源码:
let i = 0const l2 = c2.lengthlet e1 = c1.length - 1 // prev ending indexlet e2 = l2 - 1 // next ending index// 1. sync from start// (a b) c// (a b) d ewhile (i <= e1 && i <= e2) {const n1 = c1[i]const n2 = (c2[i] = optimized? cloneIfMounted(c2[i] as VNode): normalizeVNode(c2[i]))if (isSameVNodeType(n1, n2)) {patch(n1,n2,container,null,parentComponent,parentSuspense,namespace,slotScopeIds,optimized,)} else {break}i++}
以下demo为例:
i=0 新旧节点都是n1 patch打补丁更新
i=1 新旧节点都是n2 patch打补丁更新
i=2 新节点n3!== n2 跳出循环

第二步:后置预处理
从后到前去遍历新旧索引列表
发现它们的节点相同,则直接patch
打更新
否则跳出循环
并记录e1 e2的值
vue3源码:
// 2. sync from end// a (b c)// d e (b c)while (i <= e1 && i <= e2) {const n1 = c1[e1]const n2 = (c2[e2] = optimized? cloneIfMounted(c2[e2] as VNode): normalizeVNode(c2[e2]))if (isSameVNodeType(n1, n2)) {patch(n1,n2,container,null,parentComponent,parentSuspense,namespace,slotScopeIds,optimized,)} else {break}e1--e2--}
以下demo为例:
e1=6、e2=6对应都是n7节点,patch打补丁更新
e1=5、e2=5对应的新旧节点不同
跳出循环,记录e1、e2的值

第三步:处理仅有新增节点情况
当i > e1 并且i <= e2 :代表仅有新增节点
则直接patch打补丁更新
vue3源码:
// 3. common sequence + mount// (a b)// (a b) c// i = 2, e1 = 1, e2 = 2// (a b)// c (a b)// i = 0, e1 = -1, e2 = 0if (i > e1) {if (i <= e2) {const nextPos = e2 + 1const anchor = nextPos < l2 ? (c2[nextPos] as VNode).el : parentAnchorwhile (i <= e2) {patch(null,(c2[i] = optimized? cloneIfMounted(c2[i] as VNode): normalizeVNode(c2[i])),container,anchor,parentComponent,parentSuspense,namespace,slotScopeIds,optimized,)i++}}}
以下demo为例:
i = 2时,新旧节点不同,从后往前遍历
e1=2、e2=6对应都是n7节点,patch打补丁更新
e1=1、e2=5对应的新旧节点不同
这时i > e1 并且i <= e2,仅有新增节点,直接更新

第四步:处理仅有卸载节点情况
当i > e2 并且i <= e1 :代表仅有卸载节点
则直接卸载节点
vue3源码:
// 4. common sequence + unmount// (a b) c// (a b)// i = 2, e1 = 2, e2 = 1// a (b c)// (b c)// i = 0, e1 = 0, e2 = -1else if (i > e2) {while (i <= e1) {unmount(c1[i], parentComponent, parentSuspense, true)i++}}
以下demo为例:
i = 2时,新旧节点不同,从后往前遍历
e1=2、e2=6对应都是n7节点,patch打补丁更新
e1=5、e2=1对应的新旧节点不同
这时i > e2 并且i <= e1 ,仅有卸载节点,直接卸载

第五步:处理其他情况(新增、卸载、移动)
定义以下变量
| 变量 | 定义 |
|---|---|
| s1 | 记录旧节点列表要处理部分的起始位置 |
| s2 | 记录新节点列表要处理部分的起始位置 |
| keyToNewIndexMap | 新节点位置映射表;用于映射新节点与位置的映射关系 |
| newIndexToOldIndexMap | 新旧节点位置映射表;用于记录新旧节点位置的映射关系。初始值为0,如果都是0,则判定为新节点,需要挂载 |
| maxNewIndexSoFar | 当前最远位置;用于记录新节点中当前的最远位置。目的是用于判断遍历过程中是否呈现递增趋势。如果不是则代表产生了移动 |
| moved | 递减趋势,需要移动则标识为true;后续进行移动处理 |
| j | 最长递增子序列位置 |
以下demo为列:
变动点:卸载n3 / 新增n8 / 移动n6

当s1=2 : n3 ;在新节点位置映射表内没有找到;则为卸载,把该节点移除
当s1=3: n4; 在新节点位置映射表中可以找到;则将s1 + 1 = 4放在新旧节点位置映射表中。同时对比新节点位置索引和最远位置,3 > 0,则将新索引位置记录到最远位置中。最后打补丁更新

当s1=4: n5; 在新节点位置映射表中可以找到;则将s1 + 1 = 5放在新旧节点位置映射表中。同时对比新节点位置索引和最远位置,4 > 3,则将新索引位置记录到最远位置中。最后打补丁更新

当s1=5: n6; 在新节点位置映射表中可以找到;则将s1 + 1 = 6放在新旧节点位置映射表中。同时对比新节点位置索引和最远位置,2 > 4,呈现递减趋势,说明位置发生了移动,则移动标识为true。最后patch打补丁更新

到这一步已经遍历完旧节点。这时候需要根据映射表找到最长递增子序列
目的是为了让节点做最小限度的移动操作
从下图中新旧节点映射表中可以看出:最长递增子序列索引值是4/5,将其记录下来,对应的就是1/2
从后开始往前遍历新旧节点映射表
定义变量 i 记录当前新旧节点映射表位置,
定义变量 j 记录最长递增子序列位置,初始化为1

当i=3:0 对应n8节点。0代表新增,挂载该节点
当i=2: 5 对应n5节点。j=1 对应最长递增子序列。因此无需移动,直接跳转
当i=1: 4 对应n4节点。j=2对应最长递增子序列中。因此无需移动,直接跳转
当i=0: 6对应n6节点。不处于最长递增子序列当中。因此需要移动,执行移动操作
这样下来,整个过程只需要挂载n8节点、卸载n3节点、移动n6节点,其他全部原地patch更新。性能得到了极大的保证
vue3源码:
// 5. unknown sequence// [i ... e1 + 1]: a b [c d e] f g// [i ... e2 + 1]: a b [e d c h] f g// i = 2, e1 = 4, e2 = 5else {const s1 = i // prev starting indexconst s2 = i // next starting index// 5.1 build key:index map for newChildrenconst keyToNewIndexMap: Map<PropertyKey, number> = new Map()for (i = s2; i <= e2; i++) {const nextChild = (c2[i] = optimized? cloneIfMounted(c2[i] as VNode): normalizeVNode(c2[i]))if (nextChild.key != null) {if (__DEV__ && keyToNewIndexMap.has(nextChild.key)) {warn(`Duplicate keys found during update:`,JSON.stringify(nextChild.key),`Make sure keys are unique.`,)}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 presentlet jlet patched = 0const toBePatched = e2 - s2 + 1let moved = false// used to track whether any node has movedlet 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 subsequenceconst newIndexToOldIndexMap = new Array(toBePatched)for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0for (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[newIndex] as VNode,container,null,parentComponent,parentSuspense,namespace,slotScopeIds,optimized,)patched++}}// 5.3 move and mount// generate longest stable subsequence only when nodes have movedconst increasingNewIndexSequence = moved? getSequence(newIndexToOldIndexMap): EMPTY_ARRj = increasingNewIndexSequence.length - 1// looping backwards so that we can use last patched node as anchorfor (i = toBePatched - 1; i >= 0; i--) {const nextIndex = s2 + iconst nextChild = c2[nextIndex] as VNodeconst anchor =nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchorif (newIndexToOldIndexMap[i] === 0) {// mount newpatch(null,nextChild,container,anchor,parentComponent,parentSuspense,namespace,slotScopeIds,optimized,)} 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 DOM DIFF算法的整个过程
vue3源码地址: https://github.com/vuejs/core
render文件:vue3\core\packages\runtime-core\src\renderer.ts
相关文章:
vue3-dom-diff算法
vue3diff算法 什么是vue3diff算法 Vue3中的diff算法是一种用于比较虚拟DOM树之间差异的算法,其目的是为了高效地更新真实DOM,减少不必要的重渲染 主要过程 整个过程主要分为以下五步 前置预处理后置预处理仅处理新增仅处理后置处理包含新增、卸载、…...
年会抽奖Html
在这里插入图片描述 <!-- <video id"backgroundMusic" src"file:///D:/background.mp3" loop autoplay></video> --> <divstyle"width: 290px; height: 580px; margin-left: 20px; margin-top: 20px; background: url(D:/nianhu…...
ubuntu16 重启之后lvm信息丢失故障恢复
一、背景 1、问题背景 业务有一台物理开发服务器,文件系统有损坏;由于重启时没有检查,导致重启卡住。后面通过断电重新启动之后,无法进入系统;进入救援模式,注释数据盘挂载。重启之后进入系统,…...
【华为OD-E卷 - 热点网站统计 100分(python、java、c++、js、c)】
【华为OD-E卷 - 热点网站统计 100分(python、java、c、js、c)】 题目 企业路由器的统计页面,有一个功能需要动态统计公司访问最多的网页URL top N。请设计一个算法,可以高效动态统计Top N的页面 输入描述 每一行都是一个URL或…...
Ubuntu下安装Android Sdk
下载android sdk命令行工具 https://developer.android.com/studio?hlzh-cn#command-tools mkdir android-sdk cd android-sdk unzip commandlinetools-linux-11076708_latest.zip 添加环境变量到~/.bashrc export ANDROID_HOME$HOME/android-sdk export PATH$PATH:$ANDRO…...
【JVM】总结篇-类的加载篇之 类的加载器 和ClassLoader分析
文章目录 类的加载器ClassLoader自定义类加载器双亲委派机制概念源码分析优势劣势如何打破Tomcat 沙箱安全机制JDK9 双亲委派机制变化 类的加载器 获得当前类的ClassLoader clazz.getClassLoader() 获得当前线程上下文的ClassLoader Thread.currentThread().getContextClassLoa…...
怎样修改el-table主题样式
起因:el-table有主题样式,部分需要单独设置 环境:ideanodejs插件谷歌浏览器 第一步:找到scss文件: 谷歌浏览器打开表格页面,ctrlshifti打开开发者工具,点击后鼠标移动到表格单元格上单击一下…...
MySQL(二)MySQL DDL数据库定义语言
1. MySQL DDL数据库定义语言 1.1. MySQL定义语言 进入MySQL mysql -u root -p(回车后输入密码,即可进入mysq1)1.1.1. 数据库操作 (1)查看数据库 mysql>show databases;注:MySQL语句分隔符为“;” mysql库很重要它里面有…...
Spring Boot 项目启动报 NoClassDefFoundError 异常的原因分析与解决方案 - jackson 版本不一致
目录 报错: 问题分析: 解决方案: 方案 1:对 Jackson 版本进行统一 方案 2:升级 Springfox 版本 方案 3:替换 Springfox 为 springdoc-openapi(推荐) 方案 4:排除冲突的 Jack…...
原型与原型链
什么是原型(对象) 在JavaScript中,每个对象都具有一个原型对象prototype,目的是:利用原型对象实现在同一原型链中的原型方法共享 在理解原型对象前,需要先了解什么是构造函数 构造函数 用来初始化对象的…...
【Linux】信号处理
一、Linux系统信号 1、常见的系统信号 常见的Linux系统信号 信号值描述1SIGHUP挂起(hang up)进程2SIGINT中断进(interrupt)程3SIGQUIT停止(stop)进程9SIGKILL无条件终止(terminate)…...
5个不同类型的mysql数据库安装
各种社区版本下载官方地址:MySQL :: MySQL Community Downloads 一、在线YUM仓库(Linux) 选择 MySQL Yum Repository 选择对应版本下载仓库安装包(No thanks, just start my download.) 下载方法1:下载到本…...
python学习笔记—12—布尔类型、if语句
1. 布尔类型 (1) 定义 (2) 比较运算符 (3) 代码演示 1. 手动定义 bool_1 True bool_2 False print(f"bool_1的内容是:{bool_1}, 类型是:{type(bool_1)}") print(f"bool_2的内容是:{bool_2}, 类型是:{type(bool…...
分数阶傅里叶变换代码 MATLAB实现
function Faf myfrft(f, a) %分数阶傅里叶变换函数 %输入参数: %f:原始信号 %a:阶数 %输出结果: %原始信号的a阶傅里叶变换N length(f);%总采样点数 shft rem((0:N-1)fix(N/2),N)1;%此项等同于fftshift(1:N),起到翻…...
《数据结构》期末考试测试题【中】
《数据结构》期末考试测试题【中】 21.循环队列队空的判断条件为?22. 单链表的存储密度比1?23.单链表的那些操作的效率受链表长度的影响?24.顺序表中某元素的地址为?25.m叉树第K层的结点数为?26. 在双向循环链表某节点…...
openwrt 清缓存命令行
一、查看缓存 : free -m 二、清缓存:echo 3 > /proc/sys/vm/drop_caches 三、详解。 释放物理页缓存 echo 1 > /proc/sys/vm/drop_caches 释放可回收的slab对象,包含inode and dentry echo 2 > /proc/sys/vm/drop_caches 同时…...
RP2K:一个面向细粒度图像的大规模零售商品数据集
这是一种用于细粒度图像分类的新的大规模零售产品数据集。与以往专注于相对较少产品的数据集不同,我们收集了2000多种不同零售产品的35万张图像,这些图像直接在真实的零售商店的货架上拍摄。我们的数据集旨在推进零售对象识别的研究,该研究具…...
.NET Core FluentAPI
目录 约定配置 主要规则 两种配置方式 Data Annotation Fluent API Fluent API配置 Fluent API众多方法 选择 约定配置 主要规则 表名采用DbContext中的对应的DbSet的属性名。数据表列的名字采用实体类属性的名字,列的数据类型采用和实体类属性类型最兼容…...
【C++数据结构——查找】顺序查找(头歌实践教学平台习题)【合集】
目录😋 任务描述 相关知识 一、根据输入数据建立顺序表 二、顺序表的输出 三、顺序查找算法 测试说明 通关代码 测试结果 任务描述 本关任务:实现顺序查找的算法 相关知识 为了完成本关任务,你需要掌握: 根据输入数据建立…...
HTTP Scheme 通常指的是在 URL 中用于指定使用 HTTP 协议的方案(scheme)
HTTP Scheme 通常指的是在 URL 中用于指定使用 HTTP 协议的方案(scheme)。URL(统一资源定位符)中的 scheme 部分指明了访问资源所使用的协议。对于 HTTP,有两个主要的 scheme: - **http**:表示…...
云计算——弹性云计算器(ECS)
弹性云服务器:ECS 概述 云计算重构了ICT系统,云计算平台厂商推出使得厂家能够主要关注应用管理而非平台管理的云平台,包含如下主要概念。 ECS(Elastic Cloud Server):即弹性云服务器,是云计算…...
通过Wrangler CLI在worker中创建数据库和表
官方使用文档:Getting started Cloudflare D1 docs 创建数据库 在命令行中执行完成之后,会在本地和远程创建数据库: npx wranglerlatest d1 create prod-d1-tutorial 在cf中就可以看到数据库: 现在,您的Cloudfla…...
生成 Git SSH 证书
🔑 1. 生成 SSH 密钥对 在终端(Windows 使用 Git Bash,Mac/Linux 使用 Terminal)执行命令: ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" 参数说明: -t rsa&#x…...
linux 下常用变更-8
1、删除普通用户 查询用户初始UID和GIDls -l /home/ ###家目录中查看UID cat /etc/group ###此文件查看GID删除用户1.编辑文件 /etc/passwd 找到对应的行,YW343:x:0:0::/home/YW343:/bin/bash 2.将标红的位置修改为用户对应初始UID和GID: YW3…...
Android Bitmap治理全解析:从加载优化到泄漏防控的全生命周期管理
引言 Bitmap(位图)是Android应用内存占用的“头号杀手”。一张1080P(1920x1080)的图片以ARGB_8888格式加载时,内存占用高达8MB(192010804字节)。据统计,超过60%的应用OOM崩溃与Bitm…...
AI书签管理工具开发全记录(十九):嵌入资源处理
1.前言 📝 在上一篇文章中,我们完成了书签的导入导出功能。本篇文章我们研究如何处理嵌入资源,方便后续将资源打包到一个可执行文件中。 2.embed介绍 🎯 Go 1.16 引入了革命性的 embed 包,彻底改变了静态资源管理的…...
安卓基础(Java 和 Gradle 版本)
1. 设置项目的 JDK 版本 方法1:通过 Project Structure File → Project Structure... (或按 CtrlAltShiftS) 左侧选择 SDK Location 在 Gradle Settings 部分,设置 Gradle JDK 方法2:通过 Settings File → Settings... (或 CtrlAltS)…...
Ubuntu系统多网卡多相机IP设置方法
目录 1、硬件情况 2、如何设置网卡和相机IP 2.1 万兆网卡连接交换机,交换机再连相机 2.1.1 网卡设置 2.1.2 相机设置 2.3 万兆网卡直连相机 1、硬件情况 2个网卡n个相机 电脑系统信息,系统版本:Ubuntu22.04.5 LTS;内核版本…...
java高级——高阶函数、如何定义一个函数式接口类似stream流的filter
java高级——高阶函数、stream流 前情提要文章介绍一、函数伊始1.1 合格的函数1.2 有形的函数2. 函数对象2.1 函数对象——行为参数化2.2 函数对象——延迟执行 二、 函数编程语法1. 函数对象表现形式1.1 Lambda表达式1.2 方法引用(Math::max) 2 函数接口…...
Mysql故障排插与环境优化
前置知识点 最上层是一些客户端和连接服务,包含本 sock 通信和大多数jiyukehuduan/服务端工具实现的TCP/IP通信。主要完成一些简介处理、授权认证、及相关的安全方案等。在该层上引入了线程池的概念,为通过安全认证接入的客户端提供线程。同样在该层上可…...
