【Vue.js设计与实现】第三篇第9章:渲染器-简单Diff算法-阅读笔记

文章目录

    • 9.1 减少 DOM 操作的性能开销
    • 9.2 DOM 复用与 key 的作用
    • 9.3 找到需要移动的元素
    • 9.4 如何移动元素
    • 9.5 添加新元素
    • 9.6 移除不存在的元素

系列目录:【Vue.js设计与实现】阅读笔记目录

当新旧vnode 的子节点都是一组节点时,为了以最小的性能开销完成更新操作,需要比较两组子节点,用于比较的算法就叫作 Diff 算法

9.1 减少 DOM 操作的性能开销

核心 Diff 只关心新旧虚拟节点都存在一组子节点的情况

假设有新旧DOM如下:

const oldVNode = {
	type: "div",
	children: [
		{ type: "p", children: "1" },
		{ type: "p", children: "2" },
		{ type: "p", children: "3" },
	],
};

const newVNode = {
	type: "div",
	children: [
		{ type: "p", children: "4" },
		{ type: "p", children: "5" },
		{ type: "p", children: "6" },
	],
};

节点标签都一样,只是文本内容不同,可以直接更新。

patch就是更新的方法。

const patchChildren = (n1, n2, container) => {
	if (typeof n2.children === "string") {
		// ...
	} else if (Array.isArray(n2.children)) {
		//
		const oldChildren = n1.children;
		const newChildren = n2.children;
		const oldLen = oldChildren.lengt,
			newLen = newChildren.length;

		const commonLength = Math.min(oldLen, newLen);

		for (let i = 0; i < commonLength; i++) {
			patch(oldChildren[i], newChildren[i], container);
		}

		// 有新的要挂载
		if (newLen > oldLen) {
			for (let i = commonLength; i < newLen; i++) {
				patch(null, newChildren[i], container);
			}
		}
		// 有旧的要卸载
		else if (newLen < oldLen) {
			for (let i = commonLength; i < oldLen; i++) {
				unmount(oldChildren[i]);
			}
		}
	} else {
		// ...
	}
};

9.2 DOM 复用与 key 的作用

假设新旧DOM的type不完全一样:

const oldChildren = [
	{ type: "p", children: "1" },
	{ type: "div", children: "2" },
	{ type: "span", children: "3" },
];

const newChildren = [
	{ type: "span", children: "4" },
	{ type: "p", children: "5" },
	{ type: "div", children: "6" },
];

可以通过 DOM 的移动来完成子节点的更新,这要比不断地执行子节点的卸载和挂载性能更好。

需要引入额外的key作为vnode的标识:key相当于一个节点的身份证号,如果两个虚拟节点具有相同的key和vnode.type,这意味着在更新时可以复用DOM,即只需要通过移动来完成更新

const patchChildren2 = (n1, n2, container) => {
	if (typeof n2.children === "string") {
		// ...
	} else if (Array.isArray(n2.children)) {
		//
		const oldChildren = n1.children;
		const newChildren = n2.children;
		const oldLen = oldChildren.lengt,
			newLen = newChildren.length;

		// 遍历新的children
		for (let i = 0; i < newLen; i++) {
			const newVNode = newChildren[i];
			for (let j = 0; j <= oldLen; j++) {
				const oldVNode = oldChildren[j];

				// key相同:可以复用,但要更新内容
				if (newVNode.key === oldVNode.key) {
					patch(oldVNode, newVNode, container);
					break; // 找到了唯一可以复用的
				}
			}
		}
	} else {
		// ...
	}
};

9.3 找到需要移动的元素

先逆向思考,在什么情况下节点不需要移动?
答:当新旧两组节点的顺序不变时,就不需要额外的移动操作。

有例子如下:

旧:14523
新:12345

则新的节点对应的旧节点的索引是(为了方便,这里从1开始):

14523

我们找索引的递增。 索引不是递增的就要在后面插入。

上述例子的旧节点的123不需要移动,45要从旧的位置移动到新位置,即4在3的后面,5在4的后面。就得到了新节点。

使用lastIndex变量存储最大索引值:

const patchChildren3 = (n1, n2, container) => {
	if (typeof n2.children === "string") {
		// ...
	} else if (Array.isArray(n2.children)) {
		//
		const oldChildren = n1.children;
		const newChildren = n2.children;

		// 最大索引值
		let lastIndex = 0;
		for (let i = 0; i < newChildren.length; i++) {
			const newVNode = newChildren[i];

			for (let j = 0; j < oldChildren.length; j++) {
				const oldVNode = oldChildren[i];

				if (newVNode.key === oldVNode.key) {
					patch(oldVNode, newVNode, container);
					if (j < lastIndex) {
						// 说明不是递增,这里需要移动
					} else {
						// 在递增,更新lastIndex
						lastIndex = j;
					}
					break;
				}
			}
		}
	} else {
		// ...
	}
};

9.4 如何移动元素

const el=n2.el=n1.el

使用赋值语句对DOM元素进行复用。在复用了 DOM 元素之后,新节点也将持有对真实 DOM 的引用:

在这里插入图片描述
根据上一节所属,新子节点对应旧子节点索引递增的不变

上图新子节点对应旧子节点的索引为:

2 0 1

因此p-1p-2要移动:p-1加在p-3后,p-2加在p-1后:

在这里插入图片描述

9.5 添加新元素

新节点没有在旧节点找到,说明这是新元素。直接添加。

preVnode 是当前要添加节点的前一个。anchor 是要加节点的位置。

if (!find) {
	const preVnode = newChildren[i - 1];
	let anchor = null;
	if (preVnode) {
		anchor = preVnode.el.nextSibling; // 前一个的后一个
	} else {
		// 是第一个节点
		anchor = container.firstChild;
	}

	// 挂载
	patch(null, newVNode, container, anchor);
}

如图,这里的preVnodep-1

在这里插入图片描述

9.6 移除不存在的元素

直接删除不存在的节点。

完整的代码:

const patchChildren4 = (n1, n2, container) => {
	if (typeof n2.children === "string") {
		// ...
	} else if (Array.isArray(n2.children)) {
		//
		const oldChildren = n1.children;
		const newChildren = n2.children;

		let lastIndex = 0;
		for (let i = 0; i < newChildren.length; i++) {
			const newVNode = newChildren[i];

			let j = 0;
			let find = false; // 是否找到可复用的节点
			for (j; j < oldChildren.length; j++) {
				const oldVNode = oldChildren[j];
				if (newVNode.key === oldVNode.key) {
					find = true;
					patch(oldVNode, newVNode, container);

					if (j < lastIndex) {
						// 代码运行到这里,说明newVNode的真实DOM需要移动
						const preVNode = newChildren[i - 1];
						// 如果preVNode不存在,说明当前newVNode是第一个节点,不需要移动
						if (preVNode) {
							// 我们要将newVNode对应的真实DOM移到preVNode对应的真实DOM后面
							const anchor = preVNode.el.nextSibling;

							// 调用insert将newVNode对应的DOM插入到锚点前,即preNode对应的真实DOM后
							insert(newVNode.el, container, anchor);
						}
					} else {
						lastIndex = j;
					}
					break;
				}
			}

			// 新节点
			if (!find) {
				const preVnode = newChildren[i - 1];
				let anchor = null;
				if (preVnode) {
					anchor = preVnode.el.nextSibling;
				} else {
					// 是第一个节点
					anchor = container.firstChild;
				}

				// 挂载
				patch(null, newVNode, container, anchor);
			}

			// 删除要删除的节点
			for (let i = 0; i < oldChildren.length; i++) {
				const oldVNode = oldChildren[i];

				const has = newChildren.find(
					(vnode) => vnode.key === oldVNode.key
				);

				if (!has) {
					unmount(oldVNode);
				} else {
					// ...
				}
			}
		}
	} else {
		// ...
	}
};

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

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

相关文章

《深度学习》OpenCV库、Dlib库 人脸检测 案例解析

目录 一、Dlib库 1、什么是Dlib库 2、OpenCV优缺点 1&#xff09;优点 2&#xff09;缺点 3、Dlib库优缺点 1&#xff09;优点 2&#xff09;缺点 4、安装Dlib库 二、案例实现 1、对图片进行人脸识别 运行结果&#xff1a; 2、使用摄像头或对视频检测人脸 运行结…

安装和简单使用Milvus

安装和简单使用Milvus 1 介绍 Milvus是国产的高性能分布式向量数据库。 # Milvus官网 https://milvus.io/# 安装文档 https://milvus.io/docs/install-overview.md# Python的对应关系和接口文档 https://milvus.io/api-reference/pymilvus/v2.4.x/About.md2 安装Milvus 2.1…

flutter assets配置加载本地图片报错

首选列出我在照着网上说的设置assets怎么搞都报错&#xff0c;错误如下&#xff0c;搞的我想骂娘。 flutter: uses-material-design: true assets: - assets/images 后来找到了下面这个教程&#xff0c;才终于解决&#xff0c;就是要在后面加一个"/" 。 flutter这个…

北京大学与长安汽车联合发布TEOcc: 时域增强的多模态占据预测

北京大学与长安汽车联合发布TEOcc: 时域增强的多模态占据预测 Abstract 作为一种新颖的3D场景表示&#xff0c;语义占据&#xff08;semantic occupancy&#xff09;在自动驾驶领域引起了广泛关注。然而&#xff0c;现有的占据预测方法主要集中于设计更好的占据表示形式&…

scala 抽象类

理解抽象类 抽象的定义 定义一个抽象类 &#xff1a;abstract class A {} idea实例 抽象类重写 idea实例 练习 1.abstract2.错3.abstract class A{}4.对

保姆级Pinpoint(APM)实战教程

什么是Pinpoint Pinpoint是由韩国NAVER公司开发并开源的一款应用程序管理工具&#xff0c;主要针对大规模分布式系统进行性能监控和故障诊断。通过跟踪分布式应用程序之间的事务&#xff0c;帮助分析系统的整体结构以及其中的组件是如何相互连接的。 与其对标的还有Twitter的Zi…

软件模拟I2C和硬件直接驱动I2C读取TCA95系列I2C转IO芯片分析

问题描述&#xff1a; 软件读取I2C转IO信号跳变&#xff0c;低电平时能读到高电平信号&#xff0c;高电平时能读到低电平信号&#xff0c;正确信号和错误信号的比值约10:1。 原因分析&#xff1a; I2C芯片的驱动底层采用了软件模拟实现&#xff0c;没有防错机制&#xff0c;…

MongoDB 8.0已全面可用

全球广受欢迎的文档型数据库MongoDB目前最新最强的版本&#xff0c;在易用性、企业级安全性、 弹性、可用性等方面均有大幅提升&#xff0c;适用于各种应用程序。 MongoDB 8.0的优化使整体吞吐量提高了32%&#xff0c;时间序列数据聚合的处理速度提高了200%以上。MongoDB 8.0的…

SSD |(七)FTL详解(中)

文章目录 &#x1f4da;垃圾回收&#x1f407;垃圾回收原理&#x1f407;写放大&#x1f407;垃圾回收实现&#x1f407;垃圾回收时机 &#x1f4da;解除映射关系&#x1f4da;磨损均衡 &#x1f4da;垃圾回收 &#x1f407;垃圾回收原理 ✋设定一个迷你SSD空间&#xff1a; 假…

OpenAi推出ChatGPT客户端

10 月 18 日&#xff0c;继苹果 macOS 版之后&#xff0c;OpenAI 为微软 Windows 用户推出了 ChatGPT 应用桌面客户端。目前这款应用正在测试&#xff0c;ChatGPT Plus / Enterprise / Team / Edu 版本的付费用户可以在微软应用商店中下载使用。 这款应用实质上是网页版 ChatGP…

Part1_MCP4017T-502E/LT型数字变阻器使用方法

MCP4017T-502E/LT是Microchip&#xff08;微芯&#xff09;公司的一款SC70封装且具备7位单I2C™数字端口与易失性存储器数字电位器&#xff0c;通过数字接口来控制电位器的阻值大小&#xff0c;可用于需要精确调整电压分压比、信号增益控制等应用场景。相比传统的机械电位器&am…

【Java】多线程 Start() 与 run() (简洁实操)

Java系列文章目录 补充内容 Windows通过SSH连接Linux 第一章 Linux基本命令的学习与Linux历史 文章目录 Java系列文章目录一、前言二、学习内容&#xff1a;三、问题描述start() 方法run() 方法 四、解决方案&#xff1a;4.1 重复调用 .run()4.2 重复调用 start()4.3 正常调用…

初识Linux · 重定向和缓冲区

目录 前言&#xff1a; 预备知识 缓冲区 重定向 前言&#xff1a; 其实有了文件2的预备知识&#xff0c;我们已经初步了解了文件描述符fd是什么&#xff0c;底层是如何运作的了&#xff0c;那么本文&#xff0c;我们通过文件描述符对重定向和缓冲区有一个更深层次的理解&a…

鸿蒙开发案例:推箱子

推箱子游戏&#xff08;Sokoban&#xff09;的实现。游戏由多个单元格组成&#xff0c;每个单元格可以是透明的、墙或可移动的区域。游戏使用Cell类定义单元格的状态&#xff0c;如类型&#xff08;透明、墙、可移动区域&#xff09;、圆角大小及坐标偏移。而MyPosition类则用于…

三菱PLC如何实现数据排序的分析?

一、分析 将D100到D104中的据从小到大排序结果存在D100到D104中&#xff0c;如D100到D104中存入100&#xff0c;34&#xff0c;27&#xff0c;45&#xff0c;22这5个数据&#xff0c;编写一个子程序&#xff0c;只到通过调用这个子程序就可以实现这5个数据的排序。当然简单的方…

iOS IPA上传到App Store Connect的三种方案详解

引言 在iOS应用开发中&#xff0c;完成开发后的重要一步就是将IPA文件上传到App Store Connect以便进行测试或发布到App Store。无论是使用Xcode进行原生开发&#xff0c;还是通过uni-app、Flutter等跨平台工具生成的IPA文件&#xff0c;上传到App Store的流程都是类似的。苹果…

衡石分析平台系统分析人员手册-应用模版

应用模板​ 应用模板使分析成果能被快速复用&#xff0c;节省应用创作成本&#xff0c;提升应用创作效率。此外应用模板实现了应用在不同环境上快速迁移。 支持应用复制功能 用户可以从现有的分析成果关联到新的分析需求并快速完成修改。 支持应用导出为模板功能 实现多个用户…

数论的第二舞——卡特兰数

当然了&#xff0c;虽然主角是卡特兰数&#xff0c;但是我们该学的数论还是不能落下的&#xff0c;首先先来介绍一个开胃小菜线性筛 1.积性函数&#xff1a; 2.线性筛 线性筛的筛选素数的时间复杂度更低&#xff0c;可以达到O(n)的时间复杂度 将每一轮进行筛选的数 n 表示…

Threejs 实现3D 地图(02)创建3d 地图

"d3": "^7.9.0", "three": "^0.169.0", "vue": "^3.5.10" 地图数据来源&#xff1a; DataV.GeoAtlas地理小工具系列 <script setup> import {onMounted, ref} from vue import * as THREE from three im…

Spring Cloud 解决了哪些问题?

大家好&#xff0c;我是锋哥。今天分享关于【Spring Cloud 解决了哪些问题&#xff1f;】面试题&#xff1f;希望对大家有帮助&#xff1b; Spring Cloud 解决了哪些问题&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 Spring Cloud 是一个为构建分布式…