简单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.hqwc.cn/news/313420.html

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

相关文章

软件测试/测试开发丨Linux 数据处理三剑客学习笔记

一、Linux 三剑客之 grep 1、 内容检索 获取行&#xff08;单行&#xff09; grep pattern file获取内容 grep -o pattern file获取上下文 grep -A -B -C pattern file 2、 文件检索 递归搜索 grep pattern -r dir/展示匹配文件名 grep -H 111 /tmp/1只展示匹配文件名 grep …

[情商-3]:理解男女思维方式、心理诉求、情绪诉求的差别

目录 前言&#xff1a; 一、感性思维 VS 理性思维 1.1 感性思维 VS 理性思维 1.2 情绪 VS 事情本身 1.3 情绪感知 VS 逻辑推理 二、情绪的表达 2.1 表达方式 2.2 表达途径 三、不明说的情绪和倾诉诉求&#xff1a;隐藏在情绪表达方式之下的情绪与情绪诉求 3.0 快乐愉…

常见网络设备及功能详解

网络设备 - 交换机 交换机&#xff1a;距离终端用户最近的设备&#xff0c;用于终端用户接入网络、对数据帧进行交换等。 交换机的功能&#xff1a; 终端设备&#xff08;PC、服务器等&#xff09;的网络接入二层交换&#xff08;Layer 2 Switching&#xff09; 网络设备 - …

思维链COT原理探究

要进行因果分析&#xff0c;需要把思维链中的不同元素拆解开来&#xff0c;然后通过控制变量实验&#xff0c;来研究不同元素对COT效果的影响。以下两篇论文的核心差异就在于: COT的变量拆解&#xff0c;以及控制变量的实验方式。 结合两篇论文的实验结论&#xff0c;可能导致…

如何在Linux系统中安装Redis

原本Redis官网提供了Windows和Linux两个版本&#xff0c;但从 2011-12-29 以后不再更新Windows版本&#xff08;https://github.com/dmajkic/redis/downloads&#xff09;&#xff0c;加之企业生产环境通常使用Linux系统&#xff0c;所以这里在Linux系统中演示如何安装Redis。 …

十八、任务通知

1、前言 (1)所谓“任务通知”&#xff0c;可以反过来读"通知任务"。我们使用队列、信号量、事件组等等方法时&#xff0c;并不知道对方是谁。使用任务通知时&#xff0c;可以明确指定&#xff1a;通知哪个任务。 (2)使用队列、信号量、事件组时&#xff0c;我们都需…

Pycharm引用其他文件夹的py

Pycharm引用其他文件夹的py 方式1&#xff1a;包名设置为Sources ROOT 起包名的时候&#xff0c;需要在该文件夹上&#xff1a;右键 --> Mark Directory as --> Sources ROOT 标记目录为源码目录&#xff0c;就可以了。 再引用就可以了 import common from aoeweb impo…

Select工作原理

I/O多路复用是一种并发处理的机制&#xff0c;允许一个进程通过一种机制监视多个描述符&#xff0c;从而在有多个I/O操作需要处理时选择其中之一进行服务。select 函数是一种常见的实现 I/O 多路复用的系统调用&#xff0c;它允许一个进程同时监视多个文件描述符的可读性、可写…

医院安全(不良)事件报告系统源码 支持二次开发、支持源码交付

医疗不良事件报告系统源码旨在建立全面的、统一的医疗不良事件标准分类系统和患者安全术语&#xff0c;使不良事件上报管理更加标准化和科学化。通过借鉴国内外医疗不良事件报告系统的先进经验&#xff0c;根据医疗不良事件的事件类型、处理事件的不同部门&#xff0c;灵活设置…

vu3-14

第一个需求是在用户登录成功之后&#xff0c;在主页显示用户的真实姓名和性别&#xff0c;这些信息要调用后端API获取数据库里面的信息&#xff0c;第二个需求是点击菜单1&#xff0c;在表单中修改用户信息之后&#xff0c;更新到后端数据库&#xff0c;然后在主页同步更新用户…

27、web攻防——通用漏洞SQL注入Tamper脚本Base64Jsonmd5

文章目录 数字型&#xff1a;0-9。http;//localhost:8081/blog/news.php?id1 字符型&#xff1a;a-z、中文&#xff0c;需要闭合符号。http;//localhost:8081/blog/news.php?idsimple 搜索型&#xff1a;在字符型的基础上加入了通配符%。http;//localhost:8081/blog/news.…

详解Vue3中的事件监听方式

本文主要介绍Vue3中的事件监听方式。 目录 一、v-on指令二、使用符号简写三、事件修饰符四、动态事件名五、常见的监听事件六、自定义事件 在Vue3中&#xff0c;事件监听的方式与Vue2有一些不同。 下面是Vue3中事件监听方式的详细介绍&#xff1a; 一、v-on指令 Vue3中仍然使…