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 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】的文字记录版。如有不明白或者是写错的地方还希望大家可以指出来,最后码字不易,希望大家可以素质三连。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

再生龙(Clonezilla)网络克隆linux系统实现迁移——筑梦之路

官方网站&#xff1a;Clonezilla - 簡介 环境说明 源端&#xff1a;CentOS 7 操作系统的虚拟机&#xff0c;硬盘大小为 40GiB&#xff0c;分为 1GiB 的 /boot&#xff08;启动&#xff09;分区、4GiB 的 swap&#xff08;交换&#xff09;分区和 35GiB 的 /&#xff08;根&…

K8S一 k8s基础知识及实战

一 K8S 概览 1.1 K8S 是什么&#xff1f; K8S官网文档&#xff1a;https://kubernetes.io/zh/docs/home/ K8S 是Kubernetes的全称&#xff0c;源于希腊语&#xff0c;意为“舵手”或“飞行员”&#xff0c;官方称其是&#xff1a;用于自动部署、扩展和管理“容器化&#xff08…

LeetCode in Python 509. Fibonacci Number (斐波那契数)

斐波那契数实现方式有多种方法&#xff0c;最容易理解的为递归法&#xff0c;也可使用动态规划降低时间复杂度&#xff0c;本文给出递归法和动态规划两种方法的代码实现。 示例&#xff1a; 图1 斐波那契数输入输出示例 方法一&#xff1a;递归法 代码&#xff1a; class …

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

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

CoFSM基于共现尺度空间的多模态遥感图像匹配方法--论文阅读记录

目录 论文 Multi-Modal Remote Sensing Image Matching Considering Co-Occurrence Filter 参考论文&#xff1a;SIFT系列论文&#xff0c; SIFT Distinctive Image Features from Scale-Invariant Keypoints&#xff0c;作者&#xff1a;David G. Lowe 快速样本共识算法…

Leetcode 53. 最大子数组和

心路历程&#xff1a; 子数组的和是可以通过前面的和加上当前值递推获得&#xff0c;所以可以用动态规划解决这道题 注意的点&#xff1a; 1、这道题再获取最大值时res不能用0而需要用负无穷初始化 解法&#xff1a;动态规划 class Solution:def maxSubArray(self, nums: …

(十三)C++自制植物大战僵尸游戏多用户存档实现(二)

植物大战僵尸游戏开发教程专栏地址http://t.csdnimg.cn/8UFMs UserData.h 在头文件中定义了枚举类型openUserDataReturnType&#xff0c;用于表示打开用户数据文件的返回状态。FileExistError表示文件存在但是打开错误&#xff0c;FileExistCorrect表示文件在且正确&#xff0…

Python | Leetcode Python题解之第29题两数相除

题目&#xff1a; 题解&#xff1a; class Solution:def divide(self, dividend: int, divisor: int) -> int:INT_MIN, INT_MAX -2**31, 2**31 - 1# 考虑被除数为最小值的情况if dividend INT_MIN:if divisor 1:return INT_MINif divisor -1:return INT_MAX# 考虑除数为…

Pandas数据分析学习笔记

前言 开刷Pandas数据分析&#xff0c;看起来很好理解&#xff0c;不过没做笔记没敲代码心里总是不安稳&#xff0c;所以复现下课程代码并演示其中遇到的问题&#xff0c;顺便水一水笔记好了 参考资料&#xff1a; 课程视频链接&#xff1a;Pandas数据分析从入门到实战 数据…

Halo自定义页面

在使用Halo后台维护项目&#xff0c;有的页面是固定的&#xff0c;但内容需要一些自定义样式&#xff0c;内容动态编辑生成&#xff0c;这个时候就需要自定义页面; Halo版本 版本&#xff1a;2.121.首先在theme.yaml中添加自定义页面并指定文件名 spec:customTemplates:page:…

面试题集中营—GC日志简析及频繁GC的调优

如何查看GC日志 有两种方式查看GC日志&#xff0c;一种是动态命令行查看 jstat -gc <pid> 300 5 第二种就是在JVM参数中增加打印的参数&#xff0c;如下&#xff1a; -XX:PrintGCDetails -XX:PrintGCTimeStamps 表示打印每次GC的日志以及GC发生的时间 -Xloggc:gc.log …

如何解决PPT中获取加载项是灰色的,无法链接到Power BI的问题?

问题描述&#xff1a; 最近有朋友留言询问:“在尝试之前我发布的如何在PPT中展示Power BI报告的操作步骤的时候&#xff0c;想要在PPT中展示Power BI报告&#xff1f;只需这样做&#xff01; (qq.com) 碰到在PowerPoint中【获取加载项选项】是灰色&#xff0c;无法链加载Powe…

WordPress网站上添加看板娘

续接上篇——基于LNMP部署wordpress-CSDN博客 目录 一.下载并解压 二.设置头文件 修改header.php 修改配置文件footer.php 三.将你设置的主题包上传到/usr/share/nginx/html/wp-content这个目录里 四.扩展——将看板娘修改到左侧 一.下载并解压 [rootaliyun ~]# wget htt…

技术分享 | app测试中常用的Android模拟器

Emulator Emualor 是 Android Studio 自带的模拟器&#xff0c;是官方提供的工具&#xff0c;Android 开发最常使用的就是这一款。 它功能非常齐全&#xff0c;电话本、通话等功能都可正常使用。用户可以使用键盘输入&#xff0c;鼠标点击模拟器按键输入&#xff0c;甚至还可…

安卓投屏延时数据如何测试,测试工具如何写?

背景&#xff1a; 投屏其实在android等使用场景非常非常多&#xff0c;比如现在火爆小米汽车的车机&#xff0c;上面涉及到手机和车机互联画面相关都是属于投屏范围。这种跨设备的投屏场景&#xff0c;流畅的体验是最重要的&#xff0c;这里就会要求投屏中最重要的一个性能指标…

Nerf技术原理

Neural Radiance Fields (NeRF) 是一种3D场景重建技术,用于从一组稀疏的2D图像创建高质量的3D模型。这一技术基于深度学习,通过训练一个神经网络来模拟场景的体积密度和颜色分布,实现在新的视角下渲染出高质量的3D图像。 NeRF的核心原理 NeRF的核心在于使用一个全连接的神经…

开源事件通知库libevent及网络连接管理模块bufferevent详解

目录 1、libevent介绍 1.1、什么是libevent&#xff1f; 1.2、libevent特点 1.3、网络连接管理模块bufferevent 2、bufferevent有什么用&#xff1f; 3、bufferevent的整体设计与实现细节 3.1、整体概况 3.2、evbuffer与bufferevent 3.3、defer callback 4、bufferev…

听说英伟达和诺和诺德要共建医药研发超算,超算安腾默默笑了

继黄仁勋公开发表“生命科学才是未来”的观点后&#xff0c;英伟达押注生物计算赛道又有新动作。日前&#xff0c;诺和诺德基金会宣布将出资和英伟达合作&#xff0c;在丹麦建造一台专注于生成式AI应用的超级计算机&#xff0c;以推动医疗保健、生命科学和绿色转型领域的研究与…

【JavaSE进阶】08-反射机制 09-注解

1 反射机制 a) 反射的基本概念 b) Java中的类反射 c) 安全性和反射 d) 反射的两个缺点 1.1 反射的基本概念 反射的概念是由 Smith 在 1982 年首次提出的&#xff0c;主要是指程序可以访问、检测和修改它本身状 态或行为的一种能力, 并能根据自身行为的状态和结果&#…

六、项目发布 -- 2. 数据库环境准备

之前我们是采用mock方式获取这些接口&#xff0c;也就是这些接口的数据其实是固定的&#xff0c;现在我们将从数据库中来获取这些数据并且在界面上进行展示。 1、数据库环境准备 到MYSQL官网上&#xff0c;主要下载服务端&#xff0c;社区版是免费的&#xff0c;安装好MYSQL …