【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;// 遍历新的childrenfor (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 {// 在递增,更新lastIndexlastIndex = j;}break;}}}} else {// ...}
};
9.4 如何移动元素
const el=n2.el=n1.el
使用赋值语句对DOM元素进行复用。在复用了 DOM 元素之后,新节点也将持有对真实 DOM 的引用:

根据上一节所属,新子节点对应旧子节点索引递增的不变。
上图新子节点对应旧子节点的索引为:
2 0 1
因此p-1和p-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);
}
如图,这里的preVnode是p-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 {// ...}
};
相关文章:
【Vue.js设计与实现】第三篇第9章:渲染器-简单Diff算法-阅读笔记
文章目录 9.1 减少 DOM 操作的性能开销9.2 DOM 复用与 key 的作用9.3 找到需要移动的元素9.4 如何移动元素9.5 添加新元素9.6 移除不存在的元素 系列目录:【Vue.js设计与实现】阅读笔记目录 当新旧vnode 的子节点都是一组节点时,为了以最小的性能…...
服务器软件之Tomcat
服务器软件之Tomcat 服务器软件之Tomcat 服务器软件之Tomcat一、什么是Tomcat二、安装Tomcat1、前提:2、下载3、解压下载的tomcat4、tomcat启动常见错误4.1、tomcat8.0 startup报错java.util.logging.ErrorManager: 44.2、java.lang.UnsatisfiedLinkError 三、Tomca…...
Flutter包管理(三)
1、作用 在APP的实际开发过程中往往会依赖很多包,而这些包之间存在着交叉依赖、版本依赖,由开发者自己管理手动管理会非常麻烦,每种开发生态或编程官方会提供一些包的管理工具,在Flutter中我们在pubspec.yaml文件中来管理第三方依…...
CGNS资料
CGNS数据文件 资料 CFD General Notation System CGNS Converters vtkCGNSReader cgnsToFromFoam Example Computer Codes 8.1.2. CGNS Mesh Format and Multizone Interface Connectivity 8 Multizone Interface Connectivity pyvista.cgnsreader CGNS for MATLAB and Octave…...
论文阅读(十六):Deep Residual Learning for Image Recognition
文章目录 1.介绍2.基本原理3.两种残差块4.网络结构 论文:Deep Residual Learning for Image Recognition 论文链接:Deep Residual Learning for Image Recognition 代码链接:Github 1.介绍 在ResNet网络提出之前,传统的卷…...
Dubbo 序列化方式
Hession 这是dubbo的默认序列化协议,是一种二进制协议,他的特点是序列化的速度比较快,并且序列化的数据体积比较小。Hession适合于大部分场景,因此被选为dubbo的默认序列化协议。 Json Json是一种基于文本的序列化方式…...
如何替换OCP节点(二):使用 antman脚本 | OceanBase应用实践
前言: OceanBase Cloud Platform(简称OCP),是 OceanBase数据库的专属企业级数据库管理平台。 在实际生产环境中,OCP的安装通常是第一步,先搭建OCP平台,进而依赖OCP来创建、管理和监控我们的生…...
15.JVM垃圾收集算法
一、垃圾收集算法 1.分代收集理论 分代收集理论是JAVA虚拟机进行垃圾回收的一种思想,根据对象存活周期的不同将内存分成不同的几个区域;一般将JAVA堆内存分为新生代和老年代;根据每个分代特点选择不同的垃圾收集器; 在新生代中&am…...
软件工程:图书管理系统甘特图
1 实验目的 熟悉GanttProject 软件环境,能够使用GanttProject绘制甘特图,进行项目管理与规划。 2 实验内容 为小型图书管理系统项目的实施计划绘制甘特图。 小型图书管理系统项目包含登录、浏览、管理读者、管理图书资料、管理书目、登记借书、登记还书、预定图书、…...
视频的编解码格式
文章目录 视频的编解码格式概念术语视频处理流程视频封装格式视频编码格式视频编解码器,视频容器和视频文件格式之间的区别补充视频码率 参考资料 视频的编解码格式 概念术语 两大组织主导视频压缩的组织及其联合(joint)组织 ITU-T(VCEG) ITU-T的中文名称是国际电信…...
网络资源模板--Android Studio 实现简易新闻App
目录 一、项目演示 二、项目测试环境 三、项目详情 四、完整的项目源码 一、项目演示 网络资源模板--基于Android studio 实现的简易新闻App 二、项目测试环境 三、项目详情 登录页 用户输入: 提供账号和密码输入框,用户可以输入登录信息。支持“记…...
LabVIEW提高开发效率技巧----离线调试
离线调试是LabVIEW开发中一项重要的技巧,通过使用Simulate Signal Express VI生成虚拟数据,开发者能够有效减少对实际硬件的依赖,加速开发过程。这种方法不仅可以提高开发效率,还能降低成本,增强系统的灵活性。 离…...
6N137S1取反电路图
文章目录 一、前言二、6N137S1性能介绍三、应用电路图 一、前言 在硬件电路设计中需要用到隔离电路,但此引脚输出为WS2812的信号,频率有840khz,所以需要使用逻辑光耦,选用6N137S1光耦,速率能达到10Mhz,能满…...
Nullinux:一款针对Linux操作系统的安全检测工具
关于Nullinux Nullinux是一款针对Linux操作系统的安全检测工具,广大研究人员可以利用该工具针对Linux目标设备执行网络侦查和安全检测。 该工具可以通过SMB枚举目标设备的安全状况信息,其中包括操作系统信息、域信息、共享信息、目录信息和用户信息。如…...
学会这 5 个 AI 神器做字体设计,保证让你私单接到爆!
最近我在浏览 AI 绘画的相关内容时,发现不少图像都是与字体相关的,而且其中一些呈现出的艺术特效很是让人眼前一亮。 放在之前,我们需要掌握一些专业技能、并花费大量时间才能设计出精致酷炫的艺术字,但是现在却可以轻松用文本直…...
《Vue3 踩坑》expose 和 defineExpose 暴露属性或方法注意事项
选项式写法 使用 选项式API - 状态选项 - expose 一定要注意: 接下来,进一步看示例说明: 设置 expose 仅显示列出的属性/方法才能被父组件调用;代码第 2 行,父组件可访问属性 a 和 方法 myFunc01,不可访…...
10.13论文阅读
通过联合学习检测和描述关键点增强可变形局部特征 摘要 局部特征提取是计算机视觉中处理图像匹配和检索等关键任务的常用方法。大多数方法的核心理念是图像经历仿射变换,忽略了诸如非刚性形变等更复杂的效果。此外,针对非刚性对应的新兴工作仍然依赖于…...
六西格玛黑带项目:TBX-02无人机飞行稳定性提升——张驰咨询
一、项目背景与问题定义 TBX-02是该公司最新发布的消费级无人机,面向摄影爱好者和户外探险者。产品上市后,通过客户反馈和实际测试数据发现,该无人机在复杂飞行环境中,如强风或快速移动时,存在明显的飞行抖动和稳定性…...
git clone 国内镜像
比如 git clone https://github.com/HKUST-Aerial-Robotics/A-LOAM.git 改成 git clone https://gitclone.com/github.com/HKUST-Aerial-Robotics/A-LOAM.git...
【服务器虚拟化】
服务器虚拟化是一种将一台物理服务器划分为多个虚拟服务器的技术,每个虚拟服务器都可以独立运行操作系统和应用程序。下面是一个详细的教程,以KVM虚拟化为例,介绍了具体的操作步骤和执行命令。 准备工作 a. 确保你的服务器支持虚拟化技术&…...
龙虎榜——20250610
上证指数放量收阴线,个股多数下跌,盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型,指数短线有调整的需求,大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的:御银股份、雄帝科技 驱动…...
HTML 语义化
目录 HTML 语义化HTML5 新特性HTML 语义化的好处语义化标签的使用场景最佳实践 HTML 语义化 HTML5 新特性 标准答案: 语义化标签: <header>:页头<nav>:导航<main>:主要内容<article>&#x…...
黑马Mybatis
Mybatis 表现层:页面展示 业务层:逻辑处理 持久层:持久数据化保存 在这里插入图片描述 Mybatis快速入门 
IGP(Interior Gateway Protocol,内部网关协议) 是一种用于在一个自治系统(AS)内部传递路由信息的路由协议,主要用于在一个组织或机构的内部网络中决定数据包的最佳路径。与用于自治系统之间通信的 EGP&…...
【Redis技术进阶之路】「原理分析系列开篇」分析客户端和服务端网络诵信交互实现(服务端执行命令请求的过程 - 初始化服务器)
服务端执行命令请求的过程 【专栏简介】【技术大纲】【专栏目标】【目标人群】1. Redis爱好者与社区成员2. 后端开发和系统架构师3. 计算机专业的本科生及研究生 初始化服务器1. 初始化服务器状态结构初始化RedisServer变量 2. 加载相关系统配置和用户配置参数定制化配置参数案…...
什么是库存周转?如何用进销存系统提高库存周转率?
你可能听说过这样一句话: “利润不是赚出来的,是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业,很多企业看着销售不错,账上却没钱、利润也不见了,一翻库存才发现: 一堆卖不动的旧货…...
【Go】3、Go语言进阶与依赖管理
前言 本系列文章参考自稀土掘金上的 【字节内部课】公开课,做自我学习总结整理。 Go语言并发编程 Go语言原生支持并发编程,它的核心机制是 Goroutine 协程、Channel 通道,并基于CSP(Communicating Sequential Processes࿰…...
2025 后端自学UNIAPP【项目实战:旅游项目】6、我的收藏页面
代码框架视图 1、先添加一个获取收藏景点的列表请求 【在文件my_api.js文件中添加】 // 引入公共的请求封装 import http from ./my_http.js// 登录接口(适配服务端返回 Token) export const login async (code, avatar) > {const res await http…...
【服务器压力测试】本地PC电脑作为服务器运行时出现卡顿和资源紧张(Windows/Linux)
要让本地PC电脑作为服务器运行时出现卡顿和资源紧张的情况,可以通过以下几种方式模拟或触发: 1. 增加CPU负载 运行大量计算密集型任务,例如: 使用多线程循环执行复杂计算(如数学运算、加密解密等)。运行图…...
LINUX 69 FTP 客服管理系统 man 5 /etc/vsftpd/vsftpd.conf
FTP 客服管理系统 实现kefu123登录,不允许匿名访问,kefu只能访问/data/kefu目录,不能查看其他目录 创建账号密码 useradd kefu echo 123|passwd -stdin kefu [rootcode caozx26420]# echo 123|passwd --stdin kefu 更改用户 kefu 的密码…...
