简单Diff算法

简单Diff算法

渲染器的核心

Diff算法

解决的问题

比较新旧虚拟节点的子节点,实现最小化更新。

虚拟节点key属性的作用

就像虚拟节点的“身份证号”,在更新时,渲染器会通过key属性找到可复用的节点,然后尽可能地通过DOM移动操作来完成更新,避免过多地对 DOM 元素进行销毁和重建。key和type的属性值均都相同,则两个节点就是相同的,即可实现进行DOM的复用。

简单Diff算法地核心逻辑(如何寻找需要移动的节点)

拿新一组子节点中的节点去旧的一组子节点中去寻找可复用的节点。如果找到了,则记录该节点的位置索引。我们把这个索引称为最大索引。在整个更新过程中,如果一个节点的索引小于最大索引,则说明该节点需要移动。

节点的移动

使用的是insert方法,找到锚点元素进行插入操作,其中insert方法对于浏览器来说依赖于原生的insertBefore函数。

在这里插入图片描述

源码展示
function patchChildren(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[j];if (newVNode.key === oldVNode.key) {// 进行打补丁patch(oldVNode, newVNode, container);if (j < lastIndex) {// 如果当前找到的节点在旧节点中的索引值小于最大索引值lastIndex,// 说明该节点对应的真实的DOM节点需要进行移// 先获取 newVNode 的前一个vnode,即 preVNodeconst preVNode = newChildren[i - 1]// 如果 prevVNode 不存在,则说明当前的 newVNode 是第一个节点,它不需要移动if (prevVNode) {// 由于我们需要将 newVNode 对应的真实DOM移动到 prevVNode 所对应的真实DOM后面,// 所以我们需要获取到 prevVNode 所对应的真实DOM的下一个兄弟节点,以此作为锚点const anchor = prevVNode.el.nextSibling();// 调用insert方法将 newVNode 对应的真实DOM插入到锚点元素的前面// 也就是 prevVNode 对应的真实DOM的后insert(newVNode.el, prevVNode.el, anchor);}} else {// 如果当前找到的节点在旧节点中的索引值大于或等于最大索引值lastIndex,// 则更新最大索引lastIndex的值lastIndex = j;}break;}}}}
}

上面代码中,如果j < lastIndex成立,则说明当前newVNode所对应的真实DOM节点需要移动。我们需要先获取当前 newVNode 节点的前一个虚拟节点,即newChildren[i - 1],然后使用insert函数完成节点的移动,其中insert 函数依赖浏览器原生的insertBefore函数。如下:

const renderer = createRenderer({// 省略部分代码insert(el, parent, anchor = null) {// insertBefore 需要锚点元素anchorparent.insertBefore(el, anchor);}// 省略部分代码
});
添加新元素

在新一组的子节点中对应的key没有在旧一组子节点中存在的节点,即为新节点。

在这里插入图片描述

如上图所示,p-4节点是一个需要新增的节点。在遍历的过程中能够发现p-4节点的key值在旧子节点中没有对应找到(视为新增节点),需要将p-4节点对应的真实DOM挂载在p-1节点对应的真实DOM节点的后面。

源码展示

function patchChildren(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]// 在第一层循环中定义变量find,代表是否在旧的一组子节点中找到可以复用的节点// 初始值为false,代表没有找到let find = false;for (let j = 0; j < oldChildren.length; j++) {const oldVNode = oldChildren[j];if (newVNode.key === oldVNode.key) {// 找到可复用的节点,将find置为truefind = true;// 进行打补丁patch(oldVNode, newVNode, container);if (j < lastIndex) {// 如果当前找到的节点在旧节点中的索引值小于最大索引值lastIndex,// 说明该节点对应的真实的DOM节点需要进行移// 先获取 newVNode 的前一个vnode,即 preVNodeconst preVNode = newChildren[i - 1]// 如果 prevVNode 不存在,则说明当前的 newVNode 是第一个节点,它不需要移动if (prevVNode) {// 由于我们需要将 newVNode 对应的真实DOM移动到 prevVNode 所对应的真实DOM后面,// 所以我们需要获取到 prevVNode 所对应的真实DOM的下一个兄弟节点,以此作为锚点const anchor = prevVNode.el.nextSibling();// 调用insert方法将 newVNode 对应的真实DOM插入到锚点元素的前面// 也就是 prevVNode 对应的真实DOM的后insert(newVNode.el, prevVNode.el, anchor);}} else {// 如果当前找到的节点在旧节点中的索引值大于或等于最大索引值lastIndex,// 则更新最大索引lastIndex的值lastIndex = j;}break;}}// 若代码运行至此,find仍为false// 说明当前 newVNode 没有在旧一组子节点找到可复用的节点// 即 newVNode 为新增节点,需要挂载if (!find) {// 为了将新增节点挂载到正确的位置,我们需要找到锚点元素// 获取 newVNode 的前一个 vnode 节点const prevVNode = newChildren[i - 1];let anchor = null;if (prevVNode) {// 如果有前一个 vnode 节点,则使用它的下一个兄弟节点作为锚点元素anchor = prevVNode.el.nextSibling;} else {// 如果没有前一个 vnode 节点,则说明即将挂载的节点是第一个子节点// 这时我们使用容器元素的 firstChild 作为锚点anchor = container.firstChild;}// 挂载 newVNodepatch(null, newVNode, container, anchor);}}}
}

上面的代码,首先我们在外层循环中定义了find变量,它表示新一组子节点是否在旧一组子节点中找到可复用的节点。变量find初始值为false,一旦找到可复用的子节点就将find置为true。如果内层循环结束后,find值仍然为false,说明当前 newVNode 是一个新增节点,需要挂载。为了找到此节点被挂载的位置,我们要获取到锚点元素:找到 newVNode 前一个虚拟节点,即 prevNode。如果存在 prevNode 存在,那么我们就取 prevNode 节点的下一个兄弟节点对应的真实DOM元素作为锚点,进行挂载;如果不存在,则说明当前需要挂载的 newVNode 节点是第一个子节点,此时应该使用容器元素的container.firstChild作为锚点。最后将锚点 anchor 作为patch函数的第四个参数,调用 patch 函数进行挂载。

patch函数如下:

// patch 函数需要接收四个参数
// n1: 旧vnode
// n2: 新vnode
// container: 容器
// anchor: 锚点元素
function patch(n1, n2, container, anchor) {// 省略部分代码if (typeof type === 'string') {if (!n1) {// 挂载时将锚点元素作为第三个参数传递给 mountElement 函数mountElement(n2, container, anchor);} else {patchElement(n1, n2);}} else if (typeof type === Text) {// 省略部分代码} else if (typeof type === Fragment) {// 省略部分代码}
}// mountElement 函数
function mountElement(vnode, container, anchor) {// 省略部分代码// 在插入节点时,将锚点元素透传给 insert 函数insert(el, container, anchor);
}

移除不存在的元素

在这里插入图片描述

如上图所示,节点p-2是需要被删除的元素。

源码展示

    function patchChildren(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++) {// 省略部分代码}// 上一步的更新操作完成后,遍历旧的一组子节点for (let i = 0; i < oldChildren.length; i++) {const oldVNode = oldChildren[i];// 拿旧子节点 oldVNode 去新的一组子节点中寻找具有相同 key 值的节点const has = newChildren.find(ele => ele.key === oldVNode.key);if (!has) {// 如果没有找到具有相同 key 的节点,则说明需要删除该节点// 调用 unmount 函数将其卸载unmount(oldVNode);} else {// 省略部分代码}}}}

更新结束后,增加删除额外节点的逻辑来删除遗留节点。当基本的更新结束后,需要遍历旧的一组子节点,然后去新的一组子节点中去寻找具有相同 key 值的节点。如果找不到,则说明需要删除该节点(调用unmount函数将其卸载)。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

26、web攻防——通用漏洞SQL注入SqlmapOracleMongodbDB2

文章目录 OracleMongoDBsqlmap SQL注入课程体系&#xff1b; 数据库注入&#xff1a;access、mysql、mssql、oracle、mongodb、postgresql等数据类型注入&#xff1a;数字型、字符型、搜索型、加密型&#xff08;base63 json&#xff09;等提交方式注入&#xff1a;get、post、…

Javaweb-servlet

一、servlet入门 1.Servlet介绍 (1)什么是Servlet Servlet是Server Applet的简称&#xff0c;是用Java编写的是运行在 Web 服务器上的程序&#xff0c;它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。使用 Servlet&#…

【Unity动画系统】Animator有限状态机参数详解

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;Uni…

【DevOps 工具链】搭建 项目管理软件 禅道

文章目录 1、简介2、环境要求3、搭建部署环境3.1. 安装Apache服务3.2. 安装PHP环境&#xff08;以php7.0为例 &#xff09;3.3. 安装MySQL服务 4、搭建禅道4.1、下载解压4.2、 配置4.2.1、 启动4.2.2、自启动4.2.3、确认是否开机启动 5、成功安装 1、简介 禅道是国产开源项目管…

Java基础语法(注释,关键字,字面量,变量,数据类型,标识符,键盘录入,IDEA安装,类,模块,项目)

文章目录 day02 - Java基础语法1. 注释使用的技巧注意点 2. 关键字2.1 概念2.2 第一个关键字class 3. 字面量区分技巧 4. 变量4.1 什么是变量&#xff1f;4.2 变量的定义格式4.2.1 格式详解4.2.2 常用的数据类型4.2.3 变量的注意事项 4.3 变量的练习 5. 数据类型5.1 Java语言数…

机器学习作业--PCA

目录 特征约减&#xff1a; 为什么进行特征约减&#xff1f; 怎么获得更具有代表性的数据&#xff1f; 怎么找到主成分&#xff0c;满足上述条件&#xff1f; 代码&#xff1a; 学习资料&#xff1a;PCA算法 - 知乎 (zhihu.com) 特征约减&#xff1a; 将高维的特征向量X…

【Qt之Quick模块】6. QML语法详解_3 QML对象特性

概述 每一个QML对象类型都包含一组已定义的特性。当进行实例时都会包含一组特性&#xff0c;这些特性是在对象类型中定义的。 一个QML文档中的对象类型声明了一个新的类型&#xff0c;即实例出一个类型。 其中包含以下特性。 the id attribute &#xff1a; id特性property a…

vmware部署docker+springboot+MySQL(超详细)

一、前期准备 (一)安装jdk #docker search openjdk #docker pull openjdk:8 (二)确认网络 如果局域网其他终端(如手机访问),虚拟机网络连接需要选择《桥接》模式,而且,需要使用有线连接,不能使用Wi-Fi,切忌切忌! 并且要选择实际的那个有线连接。比如我这里是“R…

初始SpringBoot:详解特性和结构

&#x1f3e1;浩泽学编程&#xff1a;个人主页 &#x1f525; 推荐专栏&#xff1a;《深入浅出SpringBoot》《java项目分享》 《RabbitMQ》《Spring》《SpringMVC》 &#x1f6f8;学无止境&#xff0c;不骄不躁&#xff0c;知行合一 文章目录 前言一、SpringBoot…

数据结构,题目笔记

哈希表 线性探测再散列 【算法数据结构&#xff5c;哈希查找&#xff5c;哈希冲突&#xff5c;除留余数法&#xff5c;线形探测法&#xff5c;例题讲解】https://www.bilibili.com/video/BV1514y1P7BK?vd_source1a684a3a1b9d05485b3d6277aeeb705d 【二次探测再散列法】 【【…

安防视频监控系统EasyCVR实现H.265视频在3秒内起播的注意事项

可视化云监控平台/安防视频监控系统EasyCVR视频综合管理平台&#xff0c;采用了开放式的网络结构&#xff0c;可以提供实时远程视频监控、视频录像、录像回放与存储、告警、语音对讲、云台控制、平台级联、磁盘阵列存储、视频集中存储、云存储等丰富的视频能力&#xff0c;同时…

Hadoop安装笔记2单机/伪分布式配置_Hadoop3.1.3——备赛笔记——2024全国职业院校技能大赛“大数据应用开发”赛项——任务2:离线数据处理

紧接着上一篇博客&#xff1a;Hadoop安装笔记1&#xff1a; Hadoop安装笔记1单机/伪分布式配置_Hadoop3.1.3——备赛笔记——2024全国职业院校技能大赛“大数据应用开发”赛项——任务2&#xff1a;离线数据处理-CSDN博客https://blog.csdn.net/Zhiyilang/article/details/135…

CocoaPods安装及‘__rvm_make -j8‘处理

CocoaPods是一个用Ruby写的、负责管理iOS项目中第三方开源库的工具&#xff0c;CocoaPods能让我们集中的、统一管理第三方开源库&#xff0c;为我们节省设置和更新第三方开源库的时间。 安装步骤 1.查看ruby版本 ruby -v 2.通过rvm来安装或升级Ruby&#xff0c;依次执行 cu…

【ChatGPT 默认强化学习策略】PPO 近端策略优化算法

PPO 近端策略优化算法 PPO 概率比率裁剪 演员-评论家算法演员-评论家算法&#xff1a;多智能体强化学习核心框架概率比率裁剪&#xff1a;逐步进行变化的方法PPO 目标函数的设计重要性采样KL散度 PPO 概率比率裁剪 演员-评论家算法 论文链接&#xff1a;https://arxiv.org…

基于Vite创建简单Vue3工程

首先安装node.js环境&#xff0c;没有node.js环境&#xff0c;便没有npm命令。 1、Vue3创建执行命令 D:\TABLE\test>npm create vuelatestVue.js - The Progressive JavaScript Framework√ 请输入项目名称&#xff1a; ... vue_test √ 是否使用 TypeScript 语法&#xff…

Pix2Pix如何工作?

一、说明 在本指南中&#xff0c;我们将重点介绍 Pix2Pix [1]&#xff0c;它是用于配对图像翻译的著名且成功的深度学习模型之一。在地理空间科学中&#xff0c;这种方法可以帮助传统上不可能的广泛应用&#xff0c;在这些应用中&#xff0c;我们可能希望从一个图像域转到另一个…

Vue - 使用Element UI Upload / importExcelJs进行文件导入

1 情景一 需求背景&#xff1a;后端配合&#xff0c;点击"导入"按钮&#xff0c;弹出“导入”弹窗&#xff0c;将电脑本地Excel表格数据导入到页面中表格位置&#xff08;需要调用后端接口&#xff09;&#xff0c;而页面中表格通过后端接口获取最新数据。 实现思路…

从0到1入门C++编程——01 C++基础知识

文章目录 一、工具安装二、新建项目三、设置字体、注释、行号四、C基础知识1.数据类型2.输入输出3.运算符4.选择、循环结构5.跳转语句6.数组7.函数8.指针9.结构体 一、工具安装 学习C使用到的工具是Visual Studio&#xff0c;Visual Studio 2010旗舰版下载链接&#xff1a;点此…

【python_数据分组】

对excel按照标签进行分组&#xff0c;例如按照“开票主体和对方公司”进行分组&#xff0c;并获取对应的明细。 表格如下&#xff1a; def main(excel_data):result {}for d in excel_data:if str(d[0])str(d[1]) in result:result[str(d[0])str(d[1])].append([d[0],d[1],…

java生产设备效率管理系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 java Web生产设备效率管理系统是一套完善的java web信息管理系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为TOMCAT7.0,Myeclipse8.5开发&#xff0c;数据库为ac…