【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. 确保你的服务器支持虚拟化技术&…...
网络编程(Modbus进阶)
思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...
网络六边形受到攻击
大家读完觉得有帮助记得关注和点赞!!! 抽象 现代智能交通系统 (ITS) 的一个关键要求是能够以安全、可靠和匿名的方式从互联车辆和移动设备收集地理参考数据。Nexagon 协议建立在 IETF 定位器/ID 分离协议 (…...
python如何将word的doc另存为docx
将 DOCX 文件另存为 DOCX 格式(Python 实现) 在 Python 中,你可以使用 python-docx 库来操作 Word 文档。不过需要注意的是,.doc 是旧的 Word 格式,而 .docx 是新的基于 XML 的格式。python-docx 只能处理 .docx 格式…...
根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:
根据万维钢精英日课6的内容,使用AI(2025)可以参考以下方法: 四个洞见 模型已经比人聪明:以ChatGPT o3为代表的AI非常强大,能运用高级理论解释道理、引用最新学术论文,生成对顶尖科学家都有用的…...
【7色560页】职场可视化逻辑图高级数据分析PPT模版
7种色调职场工作汇报PPT,橙蓝、黑红、红蓝、蓝橙灰、浅蓝、浅绿、深蓝七种色调模版 【7色560页】职场可视化逻辑图高级数据分析PPT模版:职场可视化逻辑图分析PPT模版https://pan.quark.cn/s/78aeabbd92d1...
iOS性能调优实战:借助克魔(KeyMob)与常用工具深度洞察App瓶颈
在日常iOS开发过程中,性能问题往往是最令人头疼的一类Bug。尤其是在App上线前的压测阶段或是处理用户反馈的高发期,开发者往往需要面对卡顿、崩溃、能耗异常、日志混乱等一系列问题。这些问题表面上看似偶发,但背后往往隐藏着系统资源调度不当…...
【Go语言基础【13】】函数、闭包、方法
文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数(函数作为参数、返回值) 三、匿名函数与闭包1. 匿名函数(Lambda函…...
在鸿蒙HarmonyOS 5中使用DevEco Studio实现企业微信功能
1. 开发环境准备 安装DevEco Studio 3.1: 从华为开发者官网下载最新版DevEco Studio安装HarmonyOS 5.0 SDK 项目配置: // module.json5 {"module": {"requestPermissions": [{"name": "ohos.permis…...
elementUI点击浏览table所选行数据查看文档
项目场景: table按照要求特定的数据变成按钮可以点击 解决方案: <el-table-columnprop"mlname"label"名称"align"center"width"180"><template slot-scope"scope"><el-buttonv-if&qu…...
离线语音识别方案分析
随着人工智能技术的不断发展,语音识别技术也得到了广泛的应用,从智能家居到车载系统,语音识别正在改变我们与设备的交互方式。尤其是离线语音识别,由于其在没有网络连接的情况下仍然能提供稳定、准确的语音处理能力,广…...
