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

news2024/11/29 4:29:31

文章目录

    • 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 {
		// ...
	}
};

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

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

相关文章

PSPICE FOR TI笔记记录1

快捷放置器件 R旋转 连线 w,单击器件引脚方块部分 电压探测笔 创建仿真文件 Analysis Type 分析模式&#xff1a;比如时域分析&#xff0c;频域分析 Run To Time 仿真时长 Skip intial transient bias point calculation (跳过初始瞬态偏置点计算(SKIPBP))一定要勾选 编辑…

高级语言源程序转换为可执行目标文件

将高级语言源程序转换为可执行目标文件的过程通常包括以下几个主要步骤&#xff1a; ​ 1. 预处理&#xff08;Preprocessing&#xff09;&#xff1a; 由谁完成预处理器&#xff08;cpp&#xff09;操作处理源代码中的预处理指令&#xff08;如宏定义、文件包含、条件编译等&…

k8s 1.28.2 集群部署 harbor v2.11.1 接入 MinIO 对象存储

文章目录 [toc]提前准备什么是 HarborHarbor 架构描述Harbor 安装的先决条件硬件资源软件依赖端口依赖 Harbor 在 k8s 的高可用Harbor 部署Helm 编排YAML 编排创建 namespace导入镜像部署 Redis部署 PostgreSQL部署 Harbor core部署 Harbor trivy部署 Harbor jobservice部署 Ha…

《Sui区块链:重塑去中心化应用的新星与未来潜力》

目录 引言 一、Sui 1、 技术架构 2、 编程语言 3、Move起源 4、Move的几个关键点&#xff1a; 5、Move 智能合约编程语言 6、智能合约编程语言可以做什么 7、和其他编程语言有什么不同 8、 安全性 9、开发者体验 10、生态系统 11、 未来发展 总结 引言 在区块链技…

AIGC助力小学生编程梦:C++入门不再难!

文章目录 一、AIGC时代下的编程教育新趋势二、小学生C入门趣味编程的意义三、小学生C入门趣味编程的实践策略四、面临的挑战与应对策略五、AIGC技术在小学生C编程中的应用与前景《小学生C趣味编程从入门到精通》编辑推荐内容简介作者简介目录 随着人工智能生成内容&#xff08;…

基于百度智能体开发爱情三十六计

基于百度智能体开发爱情三十六计 文章目录 基于百度智能体开发爱情三十六计1. 爱情三十六计智能体2. 三十六计开发创意3. 智能体开发实践3.1 基础配置3.2 进阶配置3.3 调优心得3.4可能会遇到的问题 4. 为什么选择文心智能体平台 1. 爱情三十六计智能体 爱情三十六计 是一款基于…

DORA 机器人中间件学习教程(6)——激光点云预处理

文章目录 1 移植思路2 代码输入输出说明3 编写CmakeList.txt文件4 编写yml文件5 编译并启动节点参考资料 在DORA中通过驱动获取激光雷达数据后&#xff0c;激光点云预处理部分代码是参考了autoware官方代码并对其进行裁剪得到的&#xff0c;点云预处理主要包含三个节点&#xf…

vue3项目使用百度地图实现地图选择功能代码封装(开箱即用)

vue3项目使用百度地图实现地图选择功能代码封装方案(开箱即用) <template><div class="bmapgl">

音视频入门基础:FLV专题(15)——Video Tag简介

一、引言 根据《video_file_format_spec_v10_1.pdf》第75页&#xff0c;如果某个Tag的Tag header中的TagType值为9&#xff0c;表示该Tag为Video Tag&#xff1a; 这时StreamID之后紧接着的就是VideoTagHeader&#xff0c;也就是说这时Tag header之后的就是VideoTagHeader&…

MySQL常用命令大全

博客主页&#xff1a;长风清留扬-CSDN博客系列专栏&#xff1a;MySQL入门到入魔每天更新大数据相关方面的技术&#xff0c;分享自己的实战工作经验和学习总结&#xff0c;尽量帮助大家解决更多问题和学习更多新知识&#xff0c;欢迎评论区分享自己的看法感谢大家点赞&#x1f4…

windows mysql 8.0版本重置root密码

1.停止mysql服务 以管理员运行cmd 2.安全模式启动 mysqld --console --skip-grant-tables --shared-memory 3.修改密码 再开个cmd窗口就可以进入了&#xff1a;mysql 先进入mysql database&#xff1a;use mysql 修改密码&#xff1a;ALTER USER rootlocalhost IDENTIFIED …

FFmpeg 4.3 音视频-多路H265监控录放C++开发二 : 18.04ubuntu安装,linux 下build ffmpeg 4.3 源码 并测试

测试环境 ubuntu 18.04 64 位&#xff0c;安装vmware and ubuntu 安装后调整 分辨率&#xff1a; 让windows 可以和 linux 互相复制黏贴 sudo apt-get autoremove open-vm-tools sudo apt-get update sudo apt-get install open-vm-tools-desktop 一直Y reboot 依赖安装 sud…

快速在找到函数的实体的方法

当我们写了许多许多的函数&#xff0c;那我们怎么快速的找到他们呢 我们只需要按下ctrl&#xff0c;在点击函数名字就可以快速的找到我们想要的函数

从Apple Intelligence到远程机器人手术:更快、更安全的网络成企业业务关键

过去&#xff0c;企业的业务模式和网络架构相对简单&#xff0c;数据传输量不大&#xff0c;远程访问需求也不多。企业对网络的要求主要集中在确保基本的连通性和可用性。如今&#xff0c;企业通过将产品与各项高新技术深度融合&#xff0c;赋予传统产品活力和竞争力。以苹果公…

C++20中头文件span的使用

<span>是C20中新增加的头文件&#xff0c;此头文件是containers库的一部分。包括&#xff1a; 1.模板类std::span&#xff1a;连续对象序列的非拥有视图(view)。std::span可以具有static extent&#xff0c;在这种情况下&#xff0c;序列中的元素数量在编译时已知并以typ…

06.队列介绍+实现

目录 一、队列的概念 二、队列的实现 1、头文件定义 2、功能函数实现 3、主函数测试 一、队列的概念 队列就像吃饭排队类似&#xff0c;先来先吃&#xff0c;先进先出。 队头&#xff1a;队列的头部。 队尾&#xff1a;队列的尾部。 入队&#xff1a;在队尾操作。 出队&…

汽车免拆诊断案例 | 2023款零跑C01纯电车后备厢盖无法电动打开和关闭

故障现象  一辆2023款零跑C01纯电车&#xff0c;累计行驶里程约为2万km&#xff0c;车主进厂反映&#xff0c;后备厢盖无法电动打开和关闭。 故障诊断  接车后试车&#xff0c;操作后备厢盖外侧、驾驶人侧及遥控钥匙上的后备厢盖开启按钮&#xff0c;可以听到后备厢盖解锁的…

Dropout为何能防止过拟合?dropout和BN 在前向传播和方向传播阶段的区别?

Dropout是一种用于防止过拟合的正则化技术&#xff0c;它通过在训练过程中随机关闭一部分神经元来降低模型的复杂度和参数数量&#xff0c;从而有效地减少过拟合的风险。 以下是dropout能够防止过拟合的几个原因&#xff1a; 减少神经元间的共适应性&#xff1a; 在训练过程中&…

Redis --- 第七讲 --- 关于事务和主从复制

一、事务的认识 redis事务和mysql的事务相似&#xff0c;但是比它的简单。 原子性、一致性、持久性、隔离性。 Redis的事务和MySQL一比就是一个弟弟。 原子性&#xff1a;Redis的事务到底有没有原子性&#xff0c;存在争议。最原本的含义&#xff0c;是吧多个操作打包到一起…

一个简单的Qt Console Application计算练习程序

初步体验Qt Creator 用途&#xff1a;练习20以内2位数乘法速算的程序 功能1&#xff1a;支持用户设定题目数量 std::cout << "请输入本次练习题目数量&#xff1a;";int numProblems 0;std::string num;std::cin >> num;try {numProblems std::stoi(…