【手写 Vue2.x 源码】第三十二篇 - diff算法-乱序比对

news2024/11/13 18:14:50

一,前言

上篇,diff算法-比对优化(下),主要涉及以下几个点:

  • 介绍了儿子节点比较的流程
  • 介绍并实现了头头、尾尾、头尾、尾头4种特殊情况比对

本篇,继续介绍 diff算法-乱序比对


二,乱序比对

1,前文回顾

之前两篇主要介绍了,在进行乱序比对前针对几种特殊情况的处理,以提升比对性能:

  • 一方有儿子,一方没有儿子;
    • 老的有儿子,新的没有儿子:直接将多余的老 dom 元素删除即可;
    • 老的没有儿子,新的有儿子:直接将新的儿子节点放入对应的老节点中即可;
  • 新老节点都有儿子时,进行头头、尾尾、头尾、尾头对比;
  • 头头、尾尾、头尾、尾头均没有命中时,进行乱序比对

本篇主要介绍 diff 算法的乱序比对,目标是尽可能复用老节点,以提升渲染性能;

2,乱序比对方案

这种情况下,头头、尾尾、头尾、尾头都不相同:

image.png

理想情况下,A、B 节点是可以被复用的:

image.png

方案

以新节点为主,以老节点做参照,到老儿子集合中去找能复用的节点,再将不能复用老节点删掉;

创建映射关系

根据老儿子集合,创建一个节点 key 和索引 index 的映射关系 mapping,

用于判定节点是否可被复用:取新节点依次到老的索引列表 mapping 中查找是否存在,如果存在就复用;不存在就重新创建;

image.png

3,乱序比对过程分析

1,先比对一下头头、尾尾、头尾、尾头,没有命中:

image.png

查找 F 是否在映射关系中,不在,直接做插入操作:插入到老的头指针前面的位置

即:将 F 节点插入到 A 节点的前面,并将新的头指针向后移动:

image.png

2,再对比一下头头、尾尾、头尾、尾头,还是没有命中:

image.png

继续查找 B 是否在映射关系中,B 在映射关系中,复用 B 节点并做移动操作:将复用节点移动到头指针指向节点的前面

即:将老的 B 节点移动到 A 节点的前面,并将新的头指针向后移动:

备注:由于原来的 B 节点被移动走了,所以之前的空位置要做标记,后续指针移动至此直接跳过

image.png

3,继续比对一次头头、尾尾、头尾、尾头,这时发现头和头相同,命中了头头比对:

image.png

这时,按照头头比对的逻辑:老的头指针向后移动,新头指针也向后移动;(同理,如果这里命中了尾尾比对,就将新老尾指针都向前进行移动;)

但由于之前 B 节点已经移动到 A 节点前面了,所以老的头指针跳过原始 B 节点位置,直接移动到 C 位置:

备注:这里就使用到了之前 B 节点移动走后所做的空位置标记;

image.png

4,继续比对一次头头、尾尾、头尾、尾头,没有命中:

image.png

查找 E 是否在映射关系中,不在,直接做插入操作:插入到老的头指针前面的位置

即:将 E 节点插入到 C 节点的前面,并将新的头指针向后移动:

备注:永远是插入到老的头指针前面的位置;

image.png

5,继续比对一次头头、尾尾、头尾、尾头,没有命中:

image.png

查找 G 是否在映射关系中,不在,直接做插入操作:插入到老的头指针前面的位置;

即:将 G 节点插入到 C 节点的前面,并将新的头指针向后移动:

image.png

6,由于新儿子数组已全部比对完成,剩余的老节点直接删除即可,

依次删除“从老的头节点到老的尾节点”区域的全部节点:

image.png

所以,最终结果为 F B A E G;其中,A、B 节点实现了节点复用;


三,代码实现

1,新老节点更新示例

let render1 = compileToFunction(`<div>
    <li key="A">A</li>
    <li key="B">B</li>
    <li key="C">C</li>
    <li key="D">D</li>
</div>`);

let render2 = compileToFunction(`<div>
    <li key="F" style="color:pink">F</li>
    <li key="B" style="color:yellow">B</li>
    <li key="A" style="color:blue">A</li>
    <li key="E" style="color:red">E</li>
    <li key="P" style="color:red">P</li>
</div>`);

2,创造映射关系

根据老儿子集合创建节点 key 与索引 index 的映射关系 mapping:

// src/vdom/patch.js#updateChildren#makeKeyByIndex

function updateChildren(el, oldChildren, newChildren) {
	// ...
  
  /**
   * 根据children创建映射
   */
  function makeKeyByIndex(children) {
     let map = {}
     children.forEach((item, index)=>{
       map[item.key] = index;
     })
     console.log(map)
     debugger;
     return map
  }

  let mapping = makeKeyByIndex(oldChildren);
  // ...
}

测试:

image.png


3,处理步骤

  • 筛查:看新节点在老的里面是否存在,到 mapping 中去筛查;
  • 没有,将当前比对的新节点插入到老的头指针对用的节点前面;
  • 有,需要复用,将当前比对的老节点移动到老的头指针前面;
  • 复用步骤:插入dom、patch更新属性,原位置置空,指针移动;
while (oldStartIndex <= oldEndIndex && newStartIndex <= newEndIndex) {
  // 当前循环开始时,先处理当前的oldStartVnode和oldEndVnode为空的情况; 
  // 原因:节点之前被移走时置空,直接跳过
  if(!oldStartVnode){
    oldStartVnode = oldChildren[++oldStartIndex];
  }else if(oldEndVnode){
    oldEndVnode = oldChildren[--oldEndIndex];
  } else if (isSameVnode(oldStartVnode, newStartVnode)) {// 头头比较
    // ...
  }else if(isSameVnode(oldEndVnode, newEndVnode)){// 尾尾比较
    // ...
  }else if(isSameVnode(oldStartVnode, newEndVnode)){// 头尾比较
    // ...
  }else if(isSameVnode(oldEndVnode, newStartVnode)){// 尾头比较
    // ...
  }else{
    // 前面4种逻辑(头头、尾尾、头尾、尾头),主要是考虑到用户使用时的一些特殊场景,但也有非特殊情况,如:乱序排序
    // 筛查当前新的头指针对应的节点在mapping中是否存在
    let moveIndex = mapping[newStartVnode.key]
    if(moveIndex == undefined){// 没有,将当前比对的新节点插入到老的头指针对用的节点前面
      // 将当前新的虚拟节点创建为真实节点,插入到老的开始节点前面
      el.insertBefore(createElm(newStartVnode), oldStartVnod e.el);
    }else{  // 有,需要复用
      // 将当前比对的老节点移动到老的头指针前面
      let moveVnode = oldChildren[moveIndex];// 从老的队列中找到可以被复用的这个节点
      el.insertBefore(moveVnode.el, oldStartVnode.el);
      // 复用:位置移动完成后,还要对比并更新属性
      patch(moveVnode, oldStartVnode)
      // 由于复用的节点在oldChildren中被移走了,之前的位置要标记为空(指针移动时,跳过会使用)
      oldChildren[moveIndex] = undefined;
    }
    // 每次处理完成后,新节点的头指针都需要向后移动
    // 备注:
    // 		无论节点是否可复用,新指针都会向后移动,所以最后统一处理;
    //    节点可复用时,老节点的指针移动会在4种特殊情况中被处理完成;
    newStartVnode = newChildren[++newStartIndex];
  }
}

4,删除多余的老节点

注意:由于在新旧节点的对比时,有可能已经将部分节点移动走了,移走时置为了 undefined;

所以,此时删除多余节点时,有可能这个新老指针的区间中包含着 undefined 的节点,需要跳过对此节点的处理:

// 2,旧的多,(以旧指针为参照)删除多余的真实节点
if(oldStartIndex <= oldEndIndex){
  for(let i = oldStartIndex; i <= oldEndIndex; i++){
    let child = oldChildren[i];
    // child有值时才删除;原因:节点有可能在移走时被置为undefined
    child && el.removeChild(child.el);
  }
}

5,测试乱序比对更新

更新前:

image.png

更新后:

image.png

节点更新情况:

  • A 节点复用,只更新了颜色;
  • F、E、G 均为新增节点;
  • B 节点仅做了移动操作;

这样,就尽可能的复用了老节点;


四,结尾

本篇,diff 算法-乱序比对,主要涉及以下几个点:

  • 介绍了乱序比对的方案;
  • 介绍了乱序比对的过程分析;
  • 实现了乱序比对的代码逻辑;

下篇,diff 算法的阶段性梳理;


维护日志:

  • 20210811:
    • 二级标题与排版微调;
    • 修改有下次的图示和部分表达不够明确的语句;

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MATLAB | 全网最全边际图绘制模板(直方图、小提琴图、箱线图、雨云图、散点图... ...)

如标题所言&#xff0c;这应该是全网最全的边际图绘制模板&#xff0c;中心图有8种格式&#xff0c;边际图有11种格式&#xff0c;共计88种组合&#xff0c;另外模板中给了8款配色&#xff0c;我愿称其为888组合&#xff0c;只需要更换一下数据就能绘制出各种类型的边际图: 甚至…

中国机器视觉市场研究报告

目录 机器视觉行业概述机器视觉行业发展现状机器视觉行业典型企业分析机器视觉行业未来发展趋势 机器视觉行业概述 机器视觉定义 机器视觉&#xff08;Machine Vision&#xff0c;MV&#xff09;是人工智能正在快速发展的一个分支。根据美国制造工程师协会&#xff08;SME&…

数字孪生虚拟电厂负荷控制系统可视化

随着国家“双碳”及“构建以新能源为主体的新型电力系统”等目标的提出&#xff0c;清洁化、数字化越来越成为电力系统面临的迫切需求&#xff0c;负控系统的发展对电力营销现代化建设具有重要的意义。负控管理系统是一个着眼于全面加强电力信息管理的&#xff0c;集负荷控制、…

Tips for Confluence Administrators: Part 2

Part 1中&#xff0c;我们谈到了 Confluence 自定义配置的案例&#xff0c;例如&#xff1a;如何禁用附件下载&#xff1f;如何将iFrame放入Confluence&#xff1f;如何使我的页面完全私有&#xff1f;如何防止空间管理员删除他们的空间&#xff1f;任何软件都有bug&#xff0c…

Minecraft 1.19.2 Forge模组开发 11.Mixin

我们本次使用Mixin在1.19.2中制作一个属于自己的不死图腾。 演示效果演示效果演示效果 什么是Mixin&#xff1f; 简单来说是通过注入一些我们的代码&#xff0c;达到对MC原版内容的修改。 详细内容可以参考Minecraft 17.1 Mixin 1.首先我们需要在开发包中引入mixin的依赖&a…

深度学习——双向循环神经网络(笔记)

双向循环神经网络&#xff1a; ①对于序列来讲&#xff0c;假设的目标是&#xff1a;给定观测的情况下&#xff08;在时间序列的上下文或语言模型的上下文&#xff09;&#xff0c;对于下一个输出进行建模 ②对于序列模型来讲&#xff0c;可以从前往后看&#xff0c;也可以从…

Servlet基础

Servlet1. Servlet概述2. 快速入门3. 执行原理4. 生命周期方法5. Servlet3.06. 体系结构7. 相关配置8. HTTP8.1 概念8.2 Request8.3 Response8.4 ServletContext综合案例:文件下载&#xff1a;1. Servlet概述 Servlet是JavaEE规范(接口)之一Servlet是JavaWeb三大组件之一&…

使用sdk-npi-enablement-tool生成SVD文件和芯片头文件

使用sdk-npi-enablement-tool生成SVD文件和芯片头文件 文章目录使用sdk-npi-enablement-tool生成SVD文件和芯片头文件IntroductionOverviewOperation Steps创建芯片配置文件yaml填充外设模块的寄存器映射描述文件xlsx验证生成芯片头文件ConclusionIntroduction 芯片验证与测试…

【Linux杂篇】Linux系统终端常用配置文件更改

目录列表&#xff1a; 1.alias别名永久保存 2.解决vim文件没有颜色的问题 3.vim插件supertap插件安装&#xff08;可支持自动补全&#xff0c;非函数代码补全&#xff0c;仅支持在当前编辑文档内补全&#xff09; 4.vim插件管理 5.YCM下载 6.解决vim中使用backspace无法删…

windows安装npm和cnpm

npm: 代码的包管理器&#xff0c;但是服服器在国外&#xff0c;每一次启动项目都要下载一些依赖&#xff0c;耗时之久&#xff0c;官网下载链接戳 npm。 cnpm&#xff1a;这是淘宝团队出的npm的镜像&#xff0c;可用此代替官方的只读版本&#xff0c;官网链接 cnpm。 先安装np…

Redis6学习笔记【part3】配置文件与订阅/发布

一.Redis配置文件 1.Units单位 配置大小单位,开头定义了一些基本的度量单位&#xff0c;只支持bytes&#xff0c;不支持bit。其中 GB、Gb 大小写不敏感。 2.Include包含 类似 jsp 中的 include 引入公共页面 &#xff0c;redis 在多实例的情况也可以把公用的配置文件提取出来…

9 大指标分析 Solana 的熊市危机

Daniel, 2023 年 1 月Solana 是一个去中心化的区块链网络&#xff0c;由 Solana 实验室设计并在2020年推出&#xff0c;具有快速、可扩展和安全的特点。由于其快速的交易速度和低交易费用&#xff0c;Solana 在 2020 年和 2021 年获得了极大的关注&#xff0c;这使得它对去中心…

远程仓库操作

添加远程仓库 新建一个文件夹&#xff1a; 文件夹右键打开git bash: 初始化为git仓库&#xff1a; 在码云上新建一个git仓库&#xff1a; 复制链接&#xff1a; 在文件夹里使用git bash&#xff1a; git remote add<shortname><url> 添加一个新的远程仓库&…

制作tomcat的docker镜像

环境信息&#xff1a;MacBook Pro&#xff08;M1&#xff09;VMware-fusion(Player 版本 13.0.0 (20802013))Ubuntu 22.10tomcat镜像&#xff1a;centos-7.9.2009jdk1.8.0_341 apache-tomcat-8.5.84制作步骤&#xff1a;&#xff08;1&#xff09;下载好tomcat/jdk(我是在macbo…

学习react

这里写自定义目录标题学习React学习React 安装react的脚手架 npm i create-react-app -g通过脚手架创建demo D:\dev_project>create-react-app react-demo You are running Node 11.1.0. Create React App requires Node 14 or higher. Please update your version of No…

找不到合适好用的redis客户端工具?试试官方的客户端工具RedisInsight

这里是weihubeats,觉得文章不错可以关注公众号小奏技术&#xff0c;文章首发。拒绝营销号&#xff0c;拒绝标题党 背景 之前使用的redis客户端工具是AnotherRedisDesktopManager AnotherRedisDesktopManager github地址: https://github.com/qishibo/AnotherRedisDesktopManag…

千锋JavaScript学习笔记

千锋JavaScript学习笔记 文章目录千锋JavaScript学习笔记写在前面1. JS基础1.1 变量1.2 数据类型1.3 数据类型转换1.4 运算符1.5 条件1.6 循环1.7 函数1.8 对象数据类型1.9 数组和排序1.10 数组常用方法&#xff1a;1.11 字符串常用方法1.12 数字常用方法1.13 时间常用方法1.14…

九龙证券|三元锂离子电池和磷酸铁锂离子电池的特点和优劣势详解

动力蓄电池包括锂离子动力蓄电池、金属氢化物/镍动力蓄电池等。锂离子动力蓄电池一般简称为锂离子电池&#xff0c;锂离子电池是新能源轿车动力锂电池的重要品类&#xff0c;商场占有量也是最大的。新能源轿车商场上&#xff0c;锂离子电池常见的是磷酸铁锂离子电池和三元锂离子…

《图解HTTP》读书笔记

第一章、了解Web及网络基础 HTTP&#xff1a;HyperText Transfer Protocol&#xff0c;超文本传输协议。 HTML&#xff1a;HyperText Markup Language&#xff0c;超文本标记语言。 URL&#xff1a;Uniform Resource Locator&#xff0c;统一资源定位符。 把与互联网相关联的…

SQL题面试题

有3个表S(学生表)&#xff0c;C&#xff08;课程表&#xff09;&#xff0c;SC&#xff08;学生选课表&#xff09; S&#xff08;SNO&#xff0c;SNAME&#xff09;代表&#xff08;学号&#xff0c;姓名&#xff09; C&#xff08;CNO&#xff0c;CNAME&#xff0c;CTEACHER&…