手写vue-diff算法(三)updateChildren

前文回顾

上一篇提到,新老儿子节点比对可能存在的 3 种情况及对应的处理方法:

  • 情况 1:老的有儿子,新的没有儿子

处理方法:直接将多余的老dom元素删除即可;

  • 情况 2:老的没有儿子,新的有儿子

处理方法:直接将新的儿子节点放入对应的老节点中即可;

  • 情况 3:新老都有儿子

处理方法:执行diff比对,即:乱序比对;

针对情况 3 新老儿子节点的比对,采用了“头尾双指针”的方法

优先对新老儿子节点的“头头、尾尾、头尾、尾头”节点进行比对,若均未能命中,最后再执行乱序比对;

diff算法-都有儿子节点

1.头头对比-新序列比老序列多节点-尾部

老:A B C D
新:A B C D E

1.1基本准备

在这里插入图片描述

function updateChildren(el, oldChildren, newChildren) {// 我们操作列表 经常会是有  push shift pop unshift reverse sort这些方法  (针对这些情况做一个优化)// vue2中采用双指针的方式 比较两个节点let oldStartIndex = 0;let newStartIndex = 0;let oldEndIndex = oldChildren.length - 1;let newEndIndex = newChildren.length - 1;let oldStartVnode = oldChildren[0];let newStartVnode = newChildren[0];let oldEndVnode = oldChildren[oldEndIndex];let newEndVnode = newChildren[newEndIndex];
}

1.2 循环分析

oldStartIndex > oldEndIndex说明是新的删除了部分节点
newStartIndex > newEndIndex说明是新的增加了部分节点
当不满足以上两种情况,进行头头、尾尾、头尾、尾头、乱序对比

while (oldStartIndex <= oldEndIndex && newStartIndex <= newEndIndex) {}

1.3顺序对比

当新的头节点和旧的头节点相同时,调用patchVnode方法对子节点进行对比,同时进行属性更新,此处已经开始了递归调用
接下来将新旧头节点向后移动继续对比

while (oldStartIndex <= oldEndIndex && newStartIndex <= newEndIndex) {if (isSameVnode(oldStartVnode, newStartVnode)) {patchVnode(oldStartVnode, newStartVnode);oldStartVnode = oldChildren[++oldStartIndex]newStartVnode = newChildren[++newStartIndex]}
}

1.4 新的添加了部分节点

可能增加了好多个节点
el:旧的子节点序列
使用createElm创建新的虚拟节点,插入到el

if (newStartIndex <= newEndIndex) {// pushfor (let i = newStartIndex; i <= newEndIndex; i++) {let childEL = createElm(newChildren[i])el.appendChild(childEL)}}

2.尾尾对比-新序列比老序列少节点-尾部

老:A B C D E
新:A B C D
el调用删除子节点方法removeChild,循环进行删除

if (oldStartIndex <= oldEndIndex) {// popfor (let i = oldStartIndex; i <= oldEndIndex; i++) {let childEL = oldChildren[i].elel.removeChild(childEL)}}

3.头头对比-新序列比老序列多节点-头部

老:A B C D
新:E A B C D

从尾部进行对比,然后新旧尾节点逐渐减小

else if (isSameVnode(oldEndVnode, newEndVnode)) {patchVnode(oldEndVnode, newEndVnode);oldEndVnode = oldChildren[--oldEndIndex]newEndVnode = newChildren[--newEndIndex]}

此时插入方法需要进行修改,旧的插入方法时插入到尾部,此处需要插入到头部。

  • 如何判断是向前追加还是向后追加?
    向后追加尾指针后面没有节点,向前追加尾指针后面有节点,并且是插入到尾指针节点的前面
    if (newStartIndex <= newEndIndex) {// pushfor (let i = newStartIndex; i <= newEndIndex; i++) {let childEL = createElm(newChildren[i])let anchor = newChildren[newEndIndex + 1] ? newChildren[newEndIndex + 1].el : null; // 获取下一个元素// el.appendChild(childEL)el.insertBefore(childEL, anchor); }}

4.尾尾对比-新序列比老序列少节点-尾部

老:A B C D
新:A B C D E
同2

5.交叉对比-老序列头部和新序列尾部相同

老:D E A B C
新:A B C D

老节点序列头部和新节点序列尾部相同
对比这两个节点,将老序列头部节点后移,新序列尾部节点向前移动

else if (isSameVnode(oldStartVnode, newEndVnode)) {patchVNode(oldEndVnode, newEndVnode);el.insertBefore(oldStartVnode.el, oldEndVnode.el.nextSibling);oldStartVnode = oldChildren[++oldStartIndex]newEndVnode = newChildren[--newEndIndex]}

6.交叉对比-老序列尾部和新序列头部相同

老:A B C D
新:D A B C

老节点序列尾部和新节点序列头部相同
在老序列节点头部插入新序列尾部节点
对比这两个节点,将老序列尾部节点前移,新序列头部节点向后移动

 else if (isSameVnode(oldEndVnode, newStartVnode)) {patchVNode(oldEndVnode, newStartVnode);el.insertBefore(oldEndVnode.el, oldStartVnode.el);oldEndVnode = oldChildren[--oldEndIndex]newStartVnode = newChildren[++newStartIndex]}

7.乱序对比

老:A B C D
新:B P A C D Q N
当新老序列不满足上面的六种情况后,以老节点序列作为对比序列,从新节点序列里面挨个拿出节点进行对比
B在老节点中有,则把B插入到oldStartVnode之前,并将B原来的位置标识为undefined,新指针指向P,老序列头指针不变,为A
P没有,则将P插入到oldStartVnode之前,新指针指向A,老序列头指针不变,为A
此时新老头指针相同,情况1,新老头指针向后移动,

    function makeIndexByKey(children) {let map = {}children.forEach((child, index) => {map[child.key] = index;});return map;}let map = makeIndexByKey(oldChildren);
else {// 在给动态列表添加key的时候 要尽量避免用索引,因为索引前后都是从0 开始 , 可能会发生错误复用 // 乱序比对// 根据老的列表做一个映射关系 ,用新的去找,找到则移动,找不到则添加,最后多余的就删除let moveIndex = map[newStartVnode.key]; // 如果拿到则说明是我要移动的索引if (moveIndex !== undefined) {let moveVnode = oldChildren[moveIndex]; // 找到对应的虚拟节点 复用el.insertBefore(moveVnode.el, oldStartVnode.el);oldChildren[moveIndex] = undefined; // 表示这个节点已经移动走了patchVnode(moveVnode, newStartVnode); // 比对属性和子节点} else {el.insertBefore(createElm(newStartVnode), oldStartVnode.el);}newStartVnode = newChildren[++newStartIndex];}

code

function updateChildren(el, oldChildren, newChildren) {// 我们操作列表 经常会是有  push shift pop unshift reverse sort这些方法  (针对这些情况做一个优化)// vue2中采用双指针的方式 比较两个节点let oldStartIndex = 0;let newStartIndex = 0;let oldEndIndex = oldChildren.length - 1;let newEndIndex = newChildren.length - 1;let oldStartVnode = oldChildren[0];let newStartVnode = newChildren[0];let oldEndVnode = oldChildren[oldEndIndex];let newEndVnode = newChildren[newEndIndex];function makeIndexByKey(children) {let map = {}children.forEach((child, index) => {map[child.key] = index;});return map;}let map = makeIndexByKey(oldChildren);// 循环的时候为什么要+keywhile (oldStartIndex <= oldEndIndex && newStartIndex <= newEndIndex) { // 有任何一个不满足则停止  || 有一个为true 就继续走// 双方有一方头指针,大于尾部指针则停止循环if (!oldStartVnode) {oldStartVnode = oldChildren[++oldStartIndex]} else if (!oldEndVnode) {oldEndVnode = oldChildren[--oldEndIndex]} else if (isSameVnode(oldStartVnode, newStartVnode)) {patchVnode(oldStartVnode, newStartVnode); // 如果是相同节点 则递归比较子节点oldStartVnode = oldChildren[++oldStartIndex];newStartVnode = newChildren[++newStartIndex];// 比较开头节点} else if (isSameVnode(oldEndVnode, newEndVnode)) {patchVnode(oldEndVnode, newEndVnode); // 如果是相同节点 则递归比较子节点oldEndVnode = oldChildren[--oldEndIndex];newEndVnode = newChildren[--newEndIndex];// 比较开头节点} else if (isSameVnode(oldEndVnode, newStartVnode)) {patchVnode(oldEndVnode, newStartVnode);// insertBefore 具备移动性 会将原来的元素移动走el.insertBefore(oldEndVnode.el, oldStartVnode.el); // 将老的尾巴移动到老的前面去oldEndVnode = oldChildren[--oldEndIndex];newStartVnode = newChildren[++newStartIndex];}else if (isSameVnode(oldStartVnode, newEndVnode)) {patchVnode(oldStartVnode, newEndVnode);// insertBefore 具备移动性 会将原来的元素移动走el.insertBefore(oldStartVnode.el, oldEndVnode.el.nextSibling); // 将老的尾巴移动到老的前面去oldStartVnode = oldChildren[++oldStartIndex];newEndVnode = newChildren[--newEndIndex];} else {// 在给动态列表添加key的时候 要尽量避免用索引,因为索引前后都是从0 开始 , 可能会发生错误复用 // 乱序比对// 根据老的列表做一个映射关系 ,用新的去找,找到则移动,找不到则添加,最后多余的就删除let moveIndex = map[newStartVnode.key]; // 如果拿到则说明是我要移动的索引if (moveIndex !== undefined) {let moveVnode = oldChildren[moveIndex]; // 找到对应的虚拟节点 复用el.insertBefore(moveVnode.el, oldStartVnode.el);oldChildren[moveIndex] = undefined; // 表示这个节点已经移动走了patchVnode(moveVnode, newStartVnode); // 比对属性和子节点} else {el.insertBefore(createElm(newStartVnode), oldStartVnode.el);}newStartVnode = newChildren[++newStartIndex];}}if (newStartIndex <= newEndIndex) { // 新的多了 多余的就插入进去for (let i = newStartIndex; i <= newEndIndex; i++) {let childEl = createElm(newChildren[i])// 这里可能是像后追加 ,还有可能是向前追加let anchor = newChildren[newEndIndex + 1] ? newChildren[newEndIndex + 1].el : null; // 获取下一个元素// el.appendChild(childEl);el.insertBefore(childEl, anchor); // anchor 为null的时候则会认为是appendChild}}if (oldStartIndex <= oldEndIndex) { // 老的对了,需要删除老的for (let i = oldStartIndex; i <= oldEndIndex; i++) {if (oldChildren[i]) {let childEl = oldChildren[i].elel.removeChild(childEl);}}}// 我们为了 比较两个儿子的时候 ,增高性能 我们会有一些优化手段// 如果批量像页面中修改出入内容 浏览器会自动优化 
}

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

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

相关文章

CentOS 停服倒计时,如何打造安全好用的 Linux 系统?

导读近年来&#xff0c;操作系统在国内的讨论极其热烈&#xff0c;也备受各方关注&#xff0c;操作系统在开发者圈中的重要性越来越高。毋庸置疑&#xff0c;对于众多的开发者来说&#xff0c;选择合适的操作系统对于开发效率、代码质量和个人发展都有着非常重要的影响。CSDN 作…

基于java+swing+mysql选课管理系统V2.0

基于javaswingmysql选课管理系统V2.0 一、系统介绍二、功能展示1.项目骨架2.项目内容3.登陆4.注册界面5、主界面6、添加选课8、修改选课 四、其它1.其他系统实现五.获取源码 一、系统介绍 项目类型&#xff1a;Java SE项目&#xff08;GUI图形界面&#xff09; 项目名称&…

软件工程——第6章详细设计知识点整理

本专栏是博主个人笔记&#xff0c;主要目的是利用碎片化的时间来记忆软工知识点&#xff0c;特此声明&#xff01; 文章目录 1.详细设计阶段的根本目的是&#xff1f; 2.详细设计的任务&#xff1f; 3.详细设计的结果地位&#xff1f;如何衡量程序质量&#xff1f; 4.结构程…

每天一点Python——day45

#第四十五天 #字典元素的特点&#xff1a; #例&#xff1a;字典中的所有元素都是一个key-value对【键值对】&#xff0c;key不允许重复&#xff0c;value可以重复 a{name:张三,name:李四} print(a) #只会输出李四&#xff0c;因为键不允许重复&#xff0c;否则会出现值覆盖的情…

3.用python写网络爬虫,下载缓存

目录 3.1 为链接爬虫添加缓存支持 3.2 磁盘缓存 3.2.1 实现 3.2.2缓存测试 3.2.3节省磁盘空间 3.2.4 清理过期数据 3.2.5缺点 3.3 数据库缓存 3.3.1 NoSQL 是什么 3.3.2 安装 MangoDB 3.3.3 MongoDB 概述 3.3.4 MongoDB 缓存实现 3.3.5 压缩 3.3.6 缓存测试 3.4 本章…

【机器学习】比较全面的XGBoost算法讲解

本文是《机器学习入门基础》&#xff08;黄海广著&#xff09;的第十章的部分内容。 XGBoost算法 XGBoost是2014年2月由华盛顿大学的博士生陈天奇发明的基于梯度提升算法(GBDT)的机器学习算法&#xff0c;其算法不但具有优良的学习效果&#xff0c;而且训练速度高效&#xff0c…

spring IOC详解

一、IOC IoC就是Inversion of Control&#xff0c;控制反转。在Java开发中&#xff0c;IoC意味着将你设计好的类交给系统去控制&#xff0c;而不是在你的类内部控制。这称为控制反转。 下面我们以几个例子来说明什么是IoC。假设我们要设计一个Girl和一个Boy类&#xff0c;其中G…

【条带化】-影响磁盘性能的关键

条带化-影响磁盘性能的关键 1. 条带化简介2.影响条带化效果的两个因素2.1 条带大小&#xff08;stripe size&#xff09;2.2 条带宽度&#xff08;stripe width&#xff09;2.3 减小条带大小2.4 增加条带大小2.5 例子 总结 1. 条带化简介 当多个进程同时访问一个磁盘时&#x…

GitHub 2800颗星,支持GPT/Transformer,字节跳动这个开源项目是怎么来的?

AI 绘画、机器翻译、多轮对话……对于各类 AI 相关的功能来说&#xff0c;总有一个痛点&#xff0c;困扰着所有训模型的算法工程师们&#xff1a; 想要效果更好&#xff0c;那么 AI 模型一般都很大&#xff0c;耗费的算力更多不说&#xff0c;运行起来还更费时间&#xff1b; 如…

word文档批量生成工具(附免费软件)(按Excel表格内容自动替换内容生成文档)

批量生成word文档是让人无比厌恶但有时又不得不做的事情。比如学校要给拟录取的学生发通知书&#xff0c;就可能需要批量生成一批只有“姓名”、“学院”和“专业”不同&#xff0c;其他内容都相同的word文档以供打印&#xff08;事实上直接生成pdf是更好的选择&#xff0c;这个…

超详细Redis入门教程——Redis分布式系统

前言 本文小新为大家带来 Redis分布式系统 相关知识&#xff0c;具体内容包括数据分区算法&#xff08;包括&#xff1a;顺序分区&#xff0c;哈希分区&#xff09;&#xff0c;系统搭建与运行&#xff08;包括&#xff1a;系统搭建&#xff0c;系统启动与关闭&#xff09;&…

初学mybatis(七)缓存

学习回顾&#xff1a;初学mybatis&#xff08;六&#xff09; 一、简介 1、什么是缓存 [ Cache ]&#xff1f; 存在内存中的临时数据。将用户经常查询的数据放在缓存&#xff08;内存&#xff09;中&#xff0c;用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询&#x…