【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. 确保你的服务器支持虚拟化技术&…...
Java 语言特性(面试系列2)
一、SQL 基础 1. 复杂查询 (1)连接查询(JOIN) 内连接(INNER JOIN):返回两表匹配的记录。 SELECT e.name, d.dept_name FROM employees e INNER JOIN departments d ON e.dept_id d.dept_id; 左…...
【入坑系列】TiDB 强制索引在不同库下不生效问题
文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...
【网络安全产品大调研系列】2. 体验漏洞扫描
前言 2023 年漏洞扫描服务市场规模预计为 3.06(十亿美元)。漏洞扫描服务市场行业预计将从 2024 年的 3.48(十亿美元)增长到 2032 年的 9.54(十亿美元)。预测期内漏洞扫描服务市场 CAGR(增长率&…...
【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表
1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...
PL0语法,分析器实现!
简介 PL/0 是一种简单的编程语言,通常用于教学编译原理。它的语法结构清晰,功能包括常量定义、变量声明、过程(子程序)定义以及基本的控制结构(如条件语句和循环语句)。 PL/0 语法规范 PL/0 是一种教学用的小型编程语言,由 Niklaus Wirth 设计,用于展示编译原理的核…...
OpenLayers 分屏对比(地图联动)
注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 地图分屏对比在WebGIS开发中是很常见的功能,和卷帘图层不一样的是,分屏对比是在各个地图中添加相同或者不同的图层进行对比查看。…...
Hive 存储格式深度解析:从 TextFile 到 ORC,如何选对数据存储方案?
在大数据处理领域,Hive 作为 Hadoop 生态中重要的数据仓库工具,其存储格式的选择直接影响数据存储成本、查询效率和计算资源消耗。面对 TextFile、SequenceFile、Parquet、RCFile、ORC 等多种存储格式,很多开发者常常陷入选择困境。本文将从底…...
动态 Web 开发技术入门篇
一、HTTP 协议核心 1.1 HTTP 基础 协议全称 :HyperText Transfer Protocol(超文本传输协议) 默认端口 :HTTP 使用 80 端口,HTTPS 使用 443 端口。 请求方法 : GET :用于获取资源,…...
什么是VR全景技术
VR全景技术,全称为虚拟现实全景技术,是通过计算机图像模拟生成三维空间中的虚拟世界,使用户能够在该虚拟世界中进行全方位、无死角的观察和交互的技术。VR全景技术模拟人在真实空间中的视觉体验,结合图文、3D、音视频等多媒体元素…...
Java求职者面试指南:Spring、Spring Boot、Spring MVC与MyBatis技术解析
Java求职者面试指南:Spring、Spring Boot、Spring MVC与MyBatis技术解析 一、第一轮基础概念问题 1. Spring框架的核心容器是什么?它的作用是什么? Spring框架的核心容器是IoC(控制反转)容器。它的主要作用是管理对…...
