当前位置: 首页 > news >正文

vuejs 设计与实现 - 快速diff算法

Vue.js 2 所采用的双端 Diff 算法。既然快速 Diff 算法如此高效,我们有必要了解它的思路。接下来,我们就着重讨论快速 Diff 算法的实现原理。

相同的前置元素和后置元素

快速 Diff 算法借鉴了纯文本 Diff 算法中预处理的步骤。

案例:

旧的一组子节点:p-1、p-2、p-3。
新的一组子节点:p-1、p-4、p-2、p-3。

通过观察可以发现,两组子节点具有相同的前置节点 p-1,以及相同的后置节点 p-2 和 p-3,如图 下图 所示:
对于相同的前置节点和后置节点,由于它们在新旧两组子节点中的相对位置不变,所以我们无须移动它们,但仍然需要在它们之间打补丁。
请添加图片描述

处理前置节点

对于前置节点,我们可以建立索引 j,其初始值为 0,用来指向两组子节点的开头,如图 下图所示:
请添加图片描述

然后开启一个 while 循环,让索引 j 递增,直到遇到不相同的节点为止,如下面 patchKeyedChildren 函数的代码所示:

 function patchKeyedChildren(n1, n2, container) {const newChildren = n2.childrenconst oldChildren = n1.children// 处理相同的前置节点// 索引 j 指向新旧两组子节点的开头let j = 0let oldVNode = oldChildren[j]let newVNode = newChildren[j]// while 循环向后遍历,直到遇到拥有不同 key 值的节点为止while(oldVNode.key === newVNode.key) {// 调用 patch 函数进行更新patch(oldVNode, newVNode, container)// 更新索引 j,让其递增j++oldVNode = oldChildren[j]newVNode = newChildren[j]}}

在上面这段代码中,我们使用 while 循环查找所有相同的前置节点,并调用 patch 函数进行打补丁,直到遇到 key 值不同的节点为
止。这样,我们就完成了对前置节点的更新。在这一步更新操作过后,新旧两组子节点的状态如图 下图 所示:
请添加图片描述

处理后置节点:

这里需要注意的是,当 while 循环终止时,索引 j 的值为 1。接下来,我们需要处理相同的后置节点。由于新旧两组子节点的数量可
能不同,所以我们需要两个索引 newEnd 和 oldEnd,分别指向新旧两组子节点中的最后一个节点,如图 下图 所示:
请添加图片描述
然后,再开启一个 while 循环,并从后向前遍历这两组子节点,直到遇到 key 值不同的节点为止,如下面的代码所示:

function patchKeyedChildren(n1, n2, container) {const newChildren = n2.childrenconst oldChildren = n1.children// 处理相同的前置节点// 索引 j 指向新旧两组子节点的开头let j = 0let oldVNode = oldChildren[j]let newVNode = newChildren[j]// while 循环向后遍历,直到遇到拥有不同 key 值的节点为止while(oldVNode.key === newVNode.key) {// 调用 patch 函数进行更新patch(oldVNode, newVNode, container)// 更新索引 j,让其递增j++oldVNode = oldChildren[j]newVNode = newChildren[j]}+           // 处理后置节点:// 索引 oldEnd 指向旧的一组子节点的最后一个节点+           let oldEnd = oldChildren.length - 1// 索引 newEnd 指向新的一组子节点的最后一个节点+           let newEnd = newChildren.length - 1+           oldVNode = oldChildren[oldEnd]+           newVNode = newChildren[newEnd]// while 循环从后向前遍历,直到遇到拥有不同 key 值的节点为止+           while(oldVNode.key === newVNode.key) {// 调用 patch 函数进行更新+               patch(oldVNode, newVNode, container)// 递减 oldEnd 和 nextEnd+              oldEnd --+              newEnd--+              oldVNode = oldChildren[oldEnd]+              newVNode = newChildren[newEnd]}}

与处理相同的前置节点一样,在 while 循环内,需要调用 patch函数进行打补丁,然后递减两个索引 oldEnd、newEnd 的值。在这一步更新操作过后,新旧两组子节点的状态如图 下图 所示:
请添加图片描述

新增:

观察上图:当相同的前置节点和后置节点被处理完毕后,旧的一组子节点已经全部被处理了,而在新的一组子节点中,还遗留了一个未被处理的节点 p-4。其实不难发现,节点 p-4 是一个新增节点。那么,如何用程序得出“节点 p-4 是新增节点”这个结论呢?这需要我们观察三个索引 j、newEnd 和 oldEnd 之间的关系。

  • 条件一: oldEnd < j成立:说明在预处理过程中,所有旧子节点都处理完毕了。
  • 条件二:newEnd >= j成立:说明在预处理过后,在新的一组子 节点中,仍然有未被处理的节点,而这些遗留的节点将被视作新增节点。
    如果条件一和条件二同时成立,说明在新的一组子节点中,存在遗留节点,且这些节点都是新增节点。因此我们需要将它们挂载到正确的位置,如下图 所示:
    请添加图片描述
    在新的一组子节点中,索引值处于 j 和 newEnd 之间的任何节点都需要作为新的子节点进行挂载。那么,应该怎样将这些节点挂载到正确位置呢?这就要求我们必须找到正确的锚点元素。观察图 上图 中新的一组子节点可知,新增节点应该挂载到节点 p-2 所对应的真实DOM 前面。所以,节点 p-2 对应的真实 DOM 节点就是挂载操作的锚点元素。有了这些信息,我们就可以给出具体的代码实现了,如下所示:
function patchKeyedChildren(n1, n2, container) {const newChildren = n2.childrenconst oldChildren = n1.children// 处理相同的前置节点// 索引 j 指向新旧两组子节点的开头let j = 0let oldVNode = oldChildren[j]let newVNode = newChildren[j]// while 循环向后遍历,直到遇到拥有不同 key 值的节点为止while(oldVNode.key === newVNode.key) {// 调用 patch 函数进行更新patch(oldVNode, newVNode, container)// 更新索引 j,让其递增j++oldVNode = oldChildren[j]newVNode = newChildren[j]}// 处理后置节点:// 索引 oldEnd 指向旧的一组子节点的最后一个节点let oldEnd = oldChildren.length - 1// 索引 newEnd 指向新的一组子节点的最后一个节点let newEnd = newChildren.length - 1oldVNode = oldChildren[oldEnd]newVNode = newChildren[newEnd]// while 循环从后向前遍历,直到遇到拥有不同 key 值的节点为止while(oldVNode.key === newVNode.key) {// 调用 patch 函数进行更新patch(oldVNode, newVNode, container)// 递减 oldEnd 和 nextEndoldEnd --newEnd--oldVNode = oldChildren[oldEnd]newVNode = newChildren[newEnd]}// 新增// 预处理完毕后,如果满足如下条件,则说明从 j --> newEnd 之间的节点应作 为新节点插入+          if(j > oldEnd && j <= newEnd) {// 锚点索引+              const anchorIndex = newEnd + 1// 锚点元素+              const anchor = anchorIndex < newChildren.length ? newChildren[anchorIndex].el : null+              while(j <= newEnd) {+                  patch(null, newChildren[j++], container, anchor)+              }}}

在上面这段代码中,首先计算锚点的索引值(即 anchorIndex) 为newEnd + 1。如果小于新的一组子节点的数量,则说明锚点元素 在新的一组子节点中,所以直接使用 newChildren[anchorIndex].el 作为锚点元素;否则说明索引 newEnd 对应的节点已经是尾部节点了,这时无须提供锚点元素。有了 锚点元素之后,我们开启了一个 while 循环,用来遍历索引 j 和索引 newEnd 之间的节点,并调用 patch 函数挂载它们。

删除
案例:
  • 旧的一组子节点:p-1、p-2、p-3。
  • 新的一组子节点:p-1、p-3。

请添加图片描述
当前置节点,后置节点都处理完成,后剩余未被处理的节点如下图所示:
请添加图片描述

索引 j 和索引 oldEnd 之间的任何节点都应该被卸载,具体实现如下:

function patchKeyedChildren(n1, n2, container) {const newChildren = n2.childrenconst oldChildren = n1.children// 处理相同的前置节点// 索引 j 指向新旧两组子节点的开头let j = 0let oldVNode = oldChildren[j]let newVNode = newChildren[j]// while 循环向后遍历,直到遇到拥有不同 key 值的节点为止while(oldVNode.key === newVNode.key) {// 调用 patch 函数进行更新patch(oldVNode, newVNode, container)// 更新索引 j,让其递增j++oldVNode = oldChildren[j]newVNode = newChildren[j]}// 处理后置节点:// 索引 oldEnd 指向旧的一组子节点的最后一个节点let oldEnd = oldChildren.length - 1// 索引 newEnd 指向新的一组子节点的最后一个节点let newEnd = newChildren.length - 1oldVNode = oldChildren[oldEnd]newVNode = newChildren[newEnd]// while 循环从后向前遍历,直到遇到拥有不同 key 值的节点为止while(oldVNode.key === newVNode.key) {// 调用 patch 函数进行更新patch(oldVNode, newVNode, container)// 递减 oldEnd 和 nextEndoldEnd --newEnd--oldVNode = oldChildren[oldEnd]newVNode = newChildren[newEnd]}// 新增// 预处理完毕后,如果满足如下条件,则说明从 j --> newEnd 之间的节点应作 为新节点插入if(j > oldEnd && j <= newEnd) {// 锚点索引const anchorIndex = newEnd + 1// 锚点元素const anchor = anchorIndex < newChildren.length ? newChildren[anchorIndex].el : nullwhile(j <= newEnd) {patch(null, newChildren[j++], container, anchor)}+         } else if (j > newEnd && j <= oldEnd) {// 删除// j -> oldEnd 之间的节点应该被卸载+            while(j <= oldEnd) {+                unmount(oldChildren[j++])}}}

在上面这段代码中,我们新增了一个 else…if 分支。当满足条件j > newEnd && j <= oldEnd时,则开启一个while循环,并调用unmount 函数逐个卸载这些遗留节点。

移动

有点复杂

相关文章:

vuejs 设计与实现 - 快速diff算法

Vue.js 2 所采用的双端 Diff 算法。既然快速 Diff 算法如此高效&#xff0c;我们有必要了解它的思路。接下来&#xff0c;我们就着重讨论快速 Diff 算法的实现原理。 相同的前置元素和后置元素 快速 Diff 算法借鉴了纯文本 Diff 算法中预处理的步骤。 案例&#xff1a; 旧的…...

webpack基础知识七:说说webpack proxy工作原理?为什么能解决跨域?

一、是什么 webpack proxy&#xff0c;即webpack提供的代理服务 基本行为就是接收客户端发送的请求后转发给其他服务器 其目的是为了便于开发者在开发模式下解决跨域问题&#xff08;浏览器安全策略限制&#xff09; 想要实现代理首先需要一个中间服务器&#xff0c;webpac…...

nginx负载均衡(nginx结束)

本节主要内容 1、四层&#xff0c;七层代理的配置方法 2、负载均衡的算法 nginx负载均衡&#xff1a;反向代理来实现 反向代理有两种转发方式&#xff1a;1、四层代理 2、七层代理 Nginx的七层代理和四层代理 七层是最常见的反向代理方式&#xff0c;只能配置在nginx配置文…...

Git与Github常用方法

目录 1. Github基本使用方法2. Git使用方法3. git、VS code、Github联合使用方法4. Git配置Github远程仓库SSH密钥5 常见问题 1. Github基本使用方法 仓库&#xff08;Repository&#xff09;&#xff1a;Github上用来存放代码的空间&#xff0c;包含代码、文档和其他文件。提…...

Centos7离线安装MySQL8

1、下载MySQL https://downloads.mysql.com/archives/community/ 2、下载完毕后&#xff0c;上传到Centos&#xff0c;解压 tar -xf mysql-8.0.33-1.el7.x86_64.rpm-bundle.tar 3、逐条执行安装命令 rpm -ivh mysql-community-common-8.0.33-1.el7.x86_64.rpm rpm -ivh …...

AWD攻防学习总结(草稿状态,待陆续补充)

AWD攻防学习总结 防守端1、修改密码2、备份网站3、备份数据库4、部署WAF5、部署文件监控脚本6、部署流量监控脚本/工具7、D盾扫描&#xff0c;删除预留webshell8、代码审计&#xff0c;seay/fortify扫描&#xff0c;漏洞修复及利用9、时刻关注流量和积分信息&#xff0c;掉分时…...

扫雷(超详解+全部码源)

C语言经典游戏扫雷 前言一.游戏规则二.所需文件三.创建菜单四.游戏核心内容实现1.创建棋盘2.打印棋盘3.布置雷4.排查雷5.game()函数具体实现 五.游戏运行实操六.全部码源 前言 &#x1f600;C语言实现扫雷是对基础代码能力的考察。通过本篇文章你将学会如何制作出扫雷&#xff…...

python生成exe脚本全过程

python生成exe脚本全过程 1、定义设计的GUI界面2、几个GUI界面常用函数2.1 tk.Label2.2 tk.StringVar2.3 tk.Entry2.4 tk.Button2.5 tk.Text2.6 tk.Scrollbar 3、实例3.1 需求3.2实现 4、如何使用pycharm生成可执行exe文件4.1安装pyinstaller4.2 生成exe文件 5、生成exe过程中遇…...

【机器学习1】什么是机器学习机器学习的重要性

什么是机器学习? 简而言之&#xff0c;机器学习就是训练机器去学习。 机器学习作为人工智能(Artificial Intelligence,AI)的一个分支&#xff0c;以其最基本的形式来使用算法通过从数据中获取知识来进行预测。 不同于人类通过分析大量数据手动推导规则和模型&#xff0c;机…...

立即开始使用 3D 图像

一、说明 这个故事介绍了使用这种类型的数据来训练机器学习3D模型。特别是&#xff0c;我们讨论了Kaggle中可用的MNIST数据集的3D版本&#xff0c;以及如何使用Keras训练模型识别3D数字。 3D 数据无处不在。由于我们希望构建AI来与我们的物理世界进行交互&#xff0c;因此使用3…...

鸿鹄工程项目管理系统em Spring Cloud+Spring Boot+前后端分离构建工程项目管理系统em

​ Java版工程项目管理系统 Spring CloudSpring BootMybatisVueElementUI前后端分离 功能清单如下&#xff1a; 首页 工作台&#xff1a;待办工作、消息通知、预警信息&#xff0c;点击可进入相应的列表 项目进度图表&#xff1a;选择&#xff08;总体或单个&#xff09;项目…...

《向量数据库》——怎么安装向量检索库Faiss?

装 Faiss 以下教程将展示如何在 Linux 系统上安装 Faiss: 1. 安装 Conda。 在安装 Faiss 之前,先在系统上安装 Conda。Conda 是一个开源软件包和环境管理系统,可在 Windows、macOS 和 Linux 操作系统上运行。根据以下步骤在 Linux 系统上安装 Conda。 2. 从官网…...

学习pytorch 2 导入查看dataset

学习pytorch 2 2. dataset实战代码数据集 2. dataset实战 B站小土堆视频 代码 from torch.utils.data import Dataset from PIL import Image #import cv2 import osclass MyData(Dataset):def __init__(self, root_dir, label_dir):self.root_dir root_dirself.label_dir …...

三、kubeadm部署单Master节点kubernetes集群

kubeadm部署单Master节点kubernetes集群 一、kubernetes 1.21发布 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sGgnZuno-1691633861803)(kubeadm部署单Master节点kubernetes集群 1.21.0.assets/image-20220119160108054.png)] 1.1 介绍 2021年…...

js-6:typeof和instanceof的区别

1、typeof typeof操作符返回一个字符串&#xff0c;表示未经计算的操作数的类型。 operand表示对象或原始值的表达式&#xff0c;其类型将被返回。 从上面的例子可以看出&#xff0c;前6个都是基础数据类型&#xff0c;虽然typeof null为object&#xff0c;但这只是javascrip…...

SQL SERVER 异地备份到远程共享文件夹异常处理

SQL SERVER 异地备份到远程共享文件夹异常处理 SQL Server 异地备份到远程共享文件夹异常处理 - 灰信网&#xff08;软件开发博客聚合&#xff09; -- 允许配置高级选项 EXEC sp_configure show advanced options, 1 GO -- 重新配置 RECONFIGURE GO -- 启用xp_cmdshell EXEC sp…...

服务器数据恢复-RAID5上层Hyper-V虚拟机数据恢复案例

服务器数据恢复环境&#xff1a; 一台Windows Server服务器&#xff0c;部署Hyper-V虚拟化环境&#xff0c;虚拟机的硬盘文件和配置文件存放在一台DELL存储中。该存储中有一组由4块硬盘组建的RAID5阵列&#xff0c;用来存放虚拟机的数据文件&#xff0c;另外还有一块大容量硬盘…...

Easy Rules规则引擎(1-基础篇)

目录 一、序言二、Easy Rules介绍三、定义规则(Rules)1、规则介绍2、编程式规则定义3、声明式规则定义 四、定义事实(Facts)五、定义规则引擎(Rules Engine)1、规则引擎介绍2、InferenceRulesEngine规则引擎示例(1) 定义触发条件(2) 定义规则触发后的执行行为(3) 测试用例 一、…...

Linux 上安装部署Nacos

标题&#xff1a;在Linux上安装和部署Nacos Nacos是一个开源的分布式服务发现和配置管理平台&#xff0c;它可以帮助开发人员实现微服务架构中的服务注册、发现和动态配置管理。 步骤1&#xff1a;准备工作 在开始安装Nacos之前&#xff0c;确保您已经具备以下条件&#xff1…...

电动机的启动

1电动机启动分类 电动机启动方式包括&#xff1a;全压直接启动、自耦减压启动、Y-Δ 启动、软启动器、变频器。其中软启动器和变频器启动为潮流。当然也不是一定要使用软启动器和变频器启动&#xff0c;在运用的时候根据实际情况&#xff0c;从经济和适用性自行考虑选择。 2电…...

PP-DocLayoutV3商业应用:银行票据+政务公文+出版古籍三场景落地案例

PP-DocLayoutV3商业应用&#xff1a;银行票据政务公文出版古籍三场景落地案例 1. 新一代文档布局分析引擎的价值 在日常工作中&#xff0c;我们经常遇到各种文档处理难题&#xff1a;银行票据信息提取繁琐、政务公文格式复杂难解析、古籍文献数字化效率低下。传统OCR技术只能…...

如果AI已经会了,我们为什么还要学?

学习从来不是为了记忆知识&#xff0c;而是为了建立判断力。AI 时代&#xff0c;记忆的价值在降&#xff0c;理解的价值在涨。这个问题本身藏着一个假设&#xff1a;学习的目的是"掌握知识"。 如果这个假设成立&#xff0c;那确实&#xff0c;AI 已经把你能背的都背完…...

沈阳户外路灯厂家哪家好

大家好&#xff0c;我是你们的老朋友小李。今天咱们聊聊沈阳的户外路灯市场&#xff0c;尤其是哪家厂家值得信赖。在众多品牌中&#xff0c;中领乾路灯凭借其卓越的产品质量和贴心的服务&#xff0c;逐渐成为了行业的佼佼者。接下来&#xff0c;我会从几个方面来详细分析一下&a…...

嵌入式文件传输协议选型与优化实践

1. 嵌入式文件传输协议概述在嵌入式系统开发中&#xff0c;文件传输是设备间数据交换的基础功能。不同于PC环境&#xff0c;嵌入式设备往往受限于资源&#xff08;内存、CPU、存储&#xff09;和网络条件&#xff08;带宽、稳定性&#xff09;&#xff0c;需要专门优化的传输方…...

毕业设计实战:基于Java+MySQL的C2C商务网站设计与实现指南

毕业设计实战&#xff1a;基于JavaMySQL的C2C商务网站设计与实现指南 在开发“基于JavaMySQL的C2C商务网站”毕业设计时&#xff0c;我曾因商品订单表未通过用户ID、商品ID与收货地址ID三外键关联踩过关键坑——初期设计订单表时&#xff0c;仅记录了订单号、总价、下单时间等基…...

FLUX.1-schnell:如何用12B参数模型重塑创意产业工作流

FLUX.1-schnell&#xff1a;如何用12B参数模型重塑创意产业工作流 【免费下载链接】FLUX.1-schnell 项目地址: https://ai.gitcode.com/hf_mirrors/black-forest-labs/FLUX.1-schnell 在人工智能图像生成领域&#xff0c;一个模型的质量往往由其参数规模决定。FLUX.1-s…...

RVStarArduino:RISC-V架构下的Arduino兼容开发框架

1. RVStarArduino&#xff1a;面向RISC-V架构的Arduino兼容开发框架RVStarArduino是专为Nuclei RVStar开发板设计的Arduino兼容开发框架&#xff0c;其核心目标是将Arduino生态的易用性与RISC-V架构的硬件特性深度融合。该框架并非简单的代码移植&#xff0c;而是基于Nuclei SD…...

从付费软件到自主开发:我用AI和FFmpeg实现了一个录屏工具杖

我为什么会发出这个疑问呢&#xff1f;是因为我研究Web开发中的一个问题时&#xff0c;HTTP请求体在 Filter&#xff08;过滤器&#xff09;处被读取了之后&#xff0c;在 Controller&#xff08;控制层&#xff09;就读不到值了&#xff0c;使用 RequestBody 的时候。 无论是字…...

MySQL ER_IB_MSG_919报错解析,故障修复与远程处理指南

快速解决MySQL错误ER_IB_MSG_919 (MY-012744)的方法是备份数据文件&#xff0c;检查并修复表空间文件损坏&#xff0c;必要时使用innodb_force_recovery参数启动并导出数据重建数据库。 错误代码含义解析 ER_IB_MSG_919&#xff0c;对应内部错误代码MY-012744&#xff0c;是M…...

终极adr-tools错误处理与调试指南:7个常见问题解决方案大全

终极adr-tools错误处理与调试指南&#xff1a;7个常见问题解决方案大全 【免费下载链接】adr-tools Command-line tools for working with Architecture Decision Records 项目地址: https://gitcode.com/gh_mirrors/ad/adr-tools adr-tools是一款高效的架构决策记录&am…...