vue diff算法与虚拟dom知识整理(13) 手写patch子节点更新换位策略

news2024/9/21 2:33:48

上一文中我们编写了 patch中新旧节点都有子节点的 插入节点的逻辑
但旧节点的子节点发生顺序 或数量变化 我们还没有处理 那我们现在继续

我们先来看看 原本是怎么写的
我们打开我们的案例 找到 node_modules 下面的snabbdom/src下面的 init.ts文件
在这里插入图片描述
我们在里面找到一个 updateChildren 函数
在这里插入图片描述
这里 我们看到 这里定义了各种 新前 旧前之前变量的定义
在这里插入图片描述
然后下面还是开启循环 疯狂判断 新前旧前一不一样 然后 新前旧后是否相同等等
真的不同情况做不同处理
在这里插入图片描述
好啦 我们自己也来写一个吧

我们在案例的src跟目录下的snabbdom下创建一个patchVNode.js
然后我们打开 patch.js 找到

if(oldVnode.sel == newVnode.sel && oldVnode.key == newVnode.key) {

将里面的代码拿到patchVNode.js中
在这里插入图片描述

patchVNode.js参考代码如下

import createElement from "./createElement";
export default function(newVnode, oldVnode) {
    //判断  如果 新旧虚拟节点完全相同 则不做操作  直接返回
    if(oldVnode === newVnode) return;
    /*
        判断新节点的text是不是空的  包括就算text是空子符串  也算有 只要不是undefined未定义
        同时  也要判断  要newVnode没有children  或 children 是个空数组
    */
    if(newVnode.text != undefined&&(!newVnode.children||!newVnode.children.length)) {
        //确定一下新旧节点的text文本是不是不同  如果相同就什么都不用做了
        if(newVnode.text !== oldVnode.text) {
            //利用innerText将新节点的text写入到老节点的elm中 因为  elm  存的是真实的dom
            oldVnode.elm.innerText = newVnode.text;
        }
    }else{
    //否则就算新节点没有text
        //判断老节点有没有children
        if(oldVnode.children&&oldVnode.children.length) {
            //当新旧节点都有子节点
            //定义un用于记录当前更新的元素下标
            let un = 0;
            //遍历写节点的子元素
            newVnode.children.map((news,index) => {
                //定义 isExist  判断新节点中有没有和旧节点一样的元素
                let isExist = false;
                //遍历旧节点子集
                oldVnode.children.map(old => {
                    //判断  如果有一模一样的元素  什么都不用做
                    if(old.sel == news.sel &&old.key == news.key) {
                        isExist = true;
                    }
                })
                //判断  如果在旧节点中没有找到相同的节点 就输出出来
                if(!isExist){
                    //通过createElement将虚拟节点变成真正的孤儿节点
                    let dom = createElement(news);
                    //当当前下标的子节点的elm属性记录上这个新创建的孤儿节点
                    news.elm = dom;
                    //判断un是不是已经大于了子节点创的   如果是 表示这个下标在老节点找不到
                    if(un < oldVnode.children.length) {
                        //在老节点对应un下标的前面插入这个节点
                        oldVnode.elm.insertBefore(dom, oldVnode.children[un].elm);
                    }else{
                        //直接在老节点的最后面插入
                        oldVnode.elm.appendChild(dom);
                    }
                }else{
                    //否则就表示 本次循环中 子元素找到了相同的元素 将un+1
                    un++;
                }
            })
        } else {
            //新的有children  老的没有
            //通过innerHTML 清空老节点中的内容
            oldVnode.elm.innerHTML = "";
            //二者都有children  就算最复杂的情况
            newVnode.children.map(item => {
                let dom = createElement(item);
                oldVnode.elm.appendChild(dom);
            })
        }
    }
}

简单说 就是 将 判断为同一节点之后的 精细化处理的逻辑 全部搬到了patchVNode中

然后 我们patch.js 引入一下patchVNode

import patchVNode from "./patchVNode";

然后 刚才去掉的逻辑还是有用的
我们在去掉代码的位置调用patchVNode

patchVNode(newVnode, oldVnode);

在这里插入图片描述
当然 我们还是要验证的 看看一切是不是都还正常
我们改src下的index.js入口文件代码如下

import h from "./snabbdom/h";
import patch from "./snabbdom/patch";

const container = document.getElementById("container");

const vnode = h("section", {}, [
  h("p", {key:"a"}, "a"),
  h("p", {key:"b"}, "b"),
  h("p", {key:"c"}, "c")
]);

patch( container, vnode)

const btn = document.getElementById("btn");
const vnode1 = h("section", {},[
  h("p", {key:"a"}, "a"),
  h("p", {key:"b"}, "b"),
  h("p", {key:"c"}, "c"),
  h("p", {key:"d"}, "d")
]);

btn.onclick = function(){
  patch( vnode, vnode1)
}

运行项目
在这里插入图片描述
点击更改dom
在这里插入图片描述
可以看到功能还是一切正常的

然后 在src跟目录下的snabbdom下创建一个updateChildren.js

updateChildren中那些逻辑 我们就往这里写

我们现在 updateChildren 中写上这些代码

import patchVNode from "./patchVNode";

//判断是否为同一个虚拟节点
function checkSameVnode(a,b) {
    return a.sel == b.sel && a.key == b.key;
}

export default function(parentElm, oldch, newCh) {
    // 旧前
    let oldStartIdx = 0;
    // 新前
    let newStartIdx = 0;
    // 旧后
    let oldEndIdx = oldch.length - 1;
    // 新后
    let newEndIdx = newCh.length - 1;
    // 旧前节点
    let oldStartVnode = oldch[0];
    // 旧后节点
    let oldEndVnode = oldch[oldEndIdx];
    // 新前节点
    let newStartVnode = newCh[0];
    // 新后节点
    let newEndVnode= newCh[newEndIdx];


    /*
        定义一个while循环语句 来处理
        只要  旧前小于等于旧前 而且 新前小于等于新后 他就会一直执行
        也可以理解为  只要 新前大于了新后   或者  旧前大于了旧后  这个循环就停了
    */
    while(oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
        //判断 新节点和旧节点的第一个子节点是否相同  或者是 判断  旧前节点和新前节点是不是同一个虚拟节点
        if(checkSameVnode(oldStartVnode,newStartVnode)) {
            //直接用patchVNode 对他进行精细化比较
            patchVNode(newStartVnode,oldStartVnode);
            //将新前和旧前都  +1  新前和旧前节点都下一一个节点指向
            oldStartVnode = oldch[++oldStartIdx];
            newStartVnode = newCh[++newStartIdx];
        }
    }
}

这里 我们大概了逻辑 就是 在最开始定义好需要用来判断的 新前旧前 指向节点这些
然后 我们开启循环 处理所有的子节点
这里只写了第一判断 我们判断 新节点的第一和旧节点的第一个子节点 是不是同一个虚拟节点 如果是 就调用patchVNode继续去做精细化比较
这样 他们之前就递归起来了

然后 我们来到
patchVNode.js
先引入updateChildren

import updateChildren from "./updateChildren";

然后 我们找到

if(oldVnode.children&&oldVnode.children.length) {

将里面的代码去掉
然后调用updateChildren

updateChildren(oldVnode.elm, oldVnode.children, newVnode.children);

在这里插入图片描述
简单说 后面子节点之前的精细化比较我们自己不做了 我们交给updateChildren来做

然后 我们将 src下的index代码更改如下

import h from "./snabbdom/h";
import patch from "./snabbdom/patch";

const container = document.getElementById("container");

const vnode = h("section", {}, [
  h("p", {key:"a"}, "a"),
  h("p", {key:"b"}, "b"),
  h("p", {key:"c"}, "c")
]);

patch( container, vnode)

const btn = document.getElementById("btn");
const vnode1 = h("section", {},[
  h("p", {key:"a"}, "AAA"),
  h("p", {key:"b"}, "b"),
  h("p", {key:"c"}, "c")
]);

btn.onclick = function(){
  patch( vnode, vnode1)
}

可以看到 这些我们更改了 第一个a节点的文本
然后运行项目
在这里插入图片描述
点击更改dom

在这里插入图片描述
这样 第一个节点的精细化比较 我们就写好了

然后可以将index.js代码更改如下

import h from "./snabbdom/h";
import patch from "./snabbdom/patch";

const container = document.getElementById("container");

const vnode = h("section", {}, [
  h("p", {key:"a"}, "a"),
  h("p", {key:"b"}, "b"),
  h("p", {key:"c"}, "c")
]);

patch( container, vnode)

const btn = document.getElementById("btn");
const vnode1 = h("section", {},[
  h("p", {key:"a"}, [
    h("p", {}, "123"),
    h("p", {}, "3123"),
    h("p", {}, "121231233")
  ]),
  h("p", {key:"b"}, "b"),
  h("p", {key:"c"}, "c")
]);

btn.onclick = function(){
  patch( vnode, vnode1)
}

运行项目
在这里插入图片描述
点击更改dom

在这里插入图片描述
简单说 逻辑就是 两个节点进入patch
因为 他们都没有定义key 都是 section 标签
所以 达到了相同节点判断 走进了patchVNode
在这里插入图片描述
然后 因为 新旧节点都是没有文本 过掉第一个条件 然后 判断他们的子节这个条件
走进了 updateChildren
在这里插入图片描述
因为 新旧节点的子节点 第一个都是 p标签 key都是a 所以 走进 了这个条件 又一次用 两个第一个子节点作为参数 进行精细化比较
在这里插入图片描述
然后这里判断 我们新节点的a里面是三个子节点 所以这里判断 text的达不到
然后第二个条件 但我们旧节点的a里面是文本 没有子节点
就走到了最后的这个else
然后我们清空旧节点内容
将新节点的子节点插进去
在这里插入图片描述
然后 updateChildren.js 循环中if多加一个条件

else if(checkSameVnode(oldEndVnode, newEndVnode)) {
    //将两个节点的最后一个节点做精细化比较
    patchVnode(newEndVnode,oldEndVnode);
    //新后旧后节点与表示向前推一个
    oldEndVnode = oldch[--oldEndIdx];
    newEndVnode = newCh[--newEndIdx];
}

在这里插入图片描述
我们对 新后和旧后节点的一个处理

然后我们运行项目
在这里插入图片描述
然后点击 更改dom
在这里插入图片描述
这样 最后一个节点的精细化比较也出来了

然后 就是一个比较神奇的操作 判断旧前 和 新后
就是判断 旧节点的第一个子元素 在你新节点上 是不是跑最后面去了

我们在 updateChildren.js 循环中if多加一个条件

//然后判断  旧前与新后是不是同一个节点
}else if(checkSameVnode(oldStartVnode, newEndVnode)) {
    //拿旧后和新前做精细化比较
    patchVNode(oldStartVnode, newEndVnode);
    //用parentElm父级调用insertBefore将旧前节点  插入到旧后节点的下一个兄弟节点的前面
    parentElm.insertBefore(oldStartVnode.elm, oldEndVnode.elm.nextSibling);
    //后移旧前节点
    oldStartVnode = oldch[++oldStartIdx];
    //前移新后节点
    newEndVnode = newCh[--newEndIdx];
}

在这里插入图片描述

然后 我们将 src下的index.js代码修改如下

import h from "./snabbdom/h";
import patch from "./snabbdom/patch";

const container = document.getElementById("container");

const vnode = h("section", {}, [
  h("p", {key:"a"}, "a"),
  h("p", {key:"b"}, "b"),
  h("p", {key:"c"}, "c")
]);

patch( container, vnode)

const btn = document.getElementById("btn");
const vnode1 = h("section", {},[
  h("p", {key:"c"}, "c"),
  h("p", {key:"b"}, "b"),
  h("p", {key:"a"}, "a")
]);

btn.onclick = function(){
  patch( vnode, vnode1)
}

我们将两个节点的子节点顺序 完全颠倒过来
在这里插入图片描述
我们点击更改dom
在这里插入图片描述
这样 我们的顺序颠倒就可以了

简单讲一下逻辑 第一次进来 判断新后 旧前 他们都是 p标签 key为a
所以 拿旧前和新后做精细化比较的做小化更新 保证旧前的内容变的和新后一样
然后 这里涉及到一个 insertBefore 的知识点 如果你放入的新节点 是没有上dom树的 他会帮你插入 如果已经上树了 那么 就会变成移动
因为旧节点肯定是上过树的 所以 elm 代表这个节点在dom树上的节点
所以 这里的逻辑是 将 旧前的elm移动到旧后节点的elm的后一个兄弟节点的前面 你也可以简单理解为插入到旧后节点的后面 不需要担心这个兄弟节点拿不到 拿不到一样可以插进去
那么 此时旧后还是c节点 他后面没有节点了 所以 a就被移动到了最后面

然后第二次
新后因为被 – 指向了a的上一个节点 旧前也被下移 指向了旧节点的下一个子节点
因此 又是条件3达到了 旧前 p标签 key为b 新后 p标签 b
然后精细化处理两个节点
然后 将b插入到旧后节点的前面 因为我们并没有更改旧后节点 因为我们条件三 只是将旧前下移 新后上移
所以 尽管你把a已经插入在了c后面 c还是这个旧后节点 这样 你将b插入到 c的后一个兄弟节点 的前面 这次去那c的下一个兄弟节点有了 就是a节点 所以 b会插入在a的前面
这样 就节点的顺序就变成了 看到的 c b a
但还没完 还有一次循环呢 c进来
此时 第一个条件达到了
旧前和新前是一样的 都是这个c节点
对他们做一下精细化比较 更新c节点内容

然后就达到了我们现在的效果

然后 就还有最后一种情况
旧后与新前的匹配

//判断当旧后和新前为同一个节点时
}else if (checkSameVnode(oldEndVnode, newStartVnode)) {
    //对旧后与新前最精细化比较
    patchVNode(oldEndVnode, newStartVnode);
    //还是那个insertBefore特性 dom节点存在就是移动 不是插入  将 旧后移动到旧前的前面
    parentElm.insertBefore(oldEndVnode.elm, oldStartVnode.elm);
    //将旧后节点前移
    oldEndVnode = oldch[--oldEndIdx];
    //将新前节点后移
    newStartVnode = newCh[++newStartIdx];
}

在这里插入图片描述
然后 我们将 src下的index改成这样

import h from "./snabbdom/h";
import patch from "./snabbdom/patch";

const container = document.getElementById("container");

const vnode = h("section", {}, [
  h("p", {key:"b"}, "b"),
  h("p", {key:"a"}, "a"),
  h("p", {key:"c"}, "c")
]);

patch( container, vnode)

const btn = document.getElementById("btn");
const vnode1 = h("section", {},[
  h("p", {key:"c"}, "c"),
  h("p", {key:"b"}, "b"),
  h("p", {key:"a"}, "a")
]);

btn.onclick = function(){
  patch( vnode, vnode1)
}

在这里插入图片描述
我们点击更改dom
在这里插入图片描述
也是达到了效果 捋一下逻辑
第一次 达到了条件四 旧后和新前都是 p key为c
先对 旧后和新前做一下精细化比较
然后执行insertBefore 将旧后插入到旧前前面去
然后 将旧后向前 新前向后 加一个

然后 第二次进来 旧前没有改变 而新前在前面的逻辑被加一 指向了b 此时 新前和旧前都是b 就触发了第一个
然后后面的一次 两个a也是相同的都是做第一个 双前相同逻辑处理了

但目前还是存在一些逻辑漏洞 有时会死循环 这个 我们下次继续

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

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

相关文章

Openai+Deeplearning.AI: ChatGPT Prompt Engineering(六)

想和大家分享一下最近学习的Deeplearning.AI和openai联合打造ChatGPT Prompt Engineering在线课程.以下是我写的关于该课程的前五篇博客&#xff1a; ChatGPT Prompt Engineering(一)ChatGPT Prompt Engineering(二)ChatGPT Prompt Engineering(三)ChatGPT Prompt Engineering…

QML Text 元素

作者: 一去、二三里 个人微信号: iwaleon 微信公众号: 高效程序员 在 QML 中,Text 和 Label 是两种常用的元素(Label 继承于 Text),用于显示文本内容。虽然它们看起来很相似,但在使用和功能上有一些不同。 对于 Text 元素,比较重要的是 text 与 font 属性,一旦指定了…

Linux常见IO模型-3

在前两篇博客中&#xff0c;我们讲述了常见的IO模型&#xff0c;并对多路转接模型中的select模型进行了细致介绍和具体的代码实现。那么接下来&#xff0c;我们在本篇博客中将对剩余的多路转接模型中的&#xff1a;poll和epoll进行介绍。 目录 1.poll 1.1操作流程 1.2代码实…

硬件系统工程师宝典(27)-----MOSFET、BJT搭配,干活不累

各位同学大家好&#xff0c;欢迎继续做客电子工程学习圈&#xff0c;今天我们继续来讲这本书&#xff0c;硬件系统工程师宝典。上篇我们说到场效应管的原理是通过“监测”栅极和源极间电压来实现控制漏极-源极之间电流的目的。今天我们来讲讲场效应管如何和BJT搭配设计控制电路…

【P44】JMeter 模块控制器(Module Controller)

文章目录 一、模块控制器&#xff08;Module Controller&#xff09;参数说明二、测试计划设计 一、模块控制器&#xff08;Module Controller&#xff09;参数说明 提供了一种在运行时将测试计划片段替换为当前测试计划的机制&#xff1b;模块控制器的目标是为增加脚本的复用…

springboot+vue实验室预约设备报修管理系统

本实验室管理系统管理员功能有个人中心&#xff0c;学生管理&#xff0c;教师管理&#xff0c;公告信息管理&#xff0c;知识库管理&#xff0c;实验课程管理&#xff0c;实验室信息管理&#xff0c;实验室预约管理&#xff0c;实验设备管理&#xff0c;采购记录管理&#xff0…

addon.MediaStream,erizo::MediaStream 还有addon.MediaXXX,erizo::MediaXXX

9. MediaStream 和erizo::MediaStream 类的继承关系 erizo::MediaStream source/agent/webrtc/rtcConn/erizo/src/erizo/MediaStream.h source/agent/webrtc/rtcConn/erizo/src/erizo/MediaDefinitions.h erizo::MediaSource source/agent/webrtc/rtcConn/erizo/src/erizo/…

【C++进阶4-AVLTree】尽可能条理清晰地为你讲解比普通BST更强的——AVLTree

今天&#xff0c;带来AVLTree的讲解。文中不足错漏之处望请斧正&#xff01; 是什么 AVLTree是一种自平衡的二叉搜索树。 它通过控制左右子树的高度差不超过1来调节平衡&#xff0c;从而提高搜索&#xff0c;插入和删除的效率。 实现 结构 AVLTree为了能自动调节平衡&#…

第十四章行为性模式—策略模式

文章目录 命令模式解决的问题结构实例存在的问题适用场景 JDK源码解析 行为型模式用于描述程序在运行时复杂的流程控制&#xff0c;即描述多个类或对象之间怎样相互协作共同完成单个对象无法单独完成的任务&#xff0c;它涉及算法与对象间职责的分配。行为型模式分为类行为模式…

Java阶段三Day07

Java阶段三Day07 文章目录 Java阶段三Day07Spring Security自定义UserDetails密码加密授权 JDK包结构文档注释规范JDK APIString APIString是不可变对象String常量池内存编码及长度使用indexOf实现检索使用substring获取子串trim去除两侧空白charAtstartsWith和endsWith大小写变…

3.1 矩阵连乘问题

博主简介&#xff1a;一个爱打游戏的计算机专业学生博主主页&#xff1a; 夏驰和徐策所属专栏&#xff1a;算法设计与分析 学习目标&#xff1a; 如果我要学习动态规划中的矩阵连乘问题&#xff0c;我会采取以下学习方法&#xff1a; 1. **理解问题的背景和目标&#xff1a;首…

pytorch中分布式Collective通信API学习

随着各种大模型的不断迭出&#xff0c;感觉现在的NLP领域已经来到了大模型的时代。那么怎么来训练一个大模型呢&#xff1f;其中比较关键的一个技术基础就是分布式训练。在阅读GLM源码的时候&#xff0c;感觉其中的分布式训练代码不是很熟悉&#xff0c;看起来有点吃力&#xf…

【Nature 2023】Computational approaches streamlining drug discovery

今天为大家介绍的是一篇发表于“Nature”上面的综述文章“Computational approaches streamlining drug discovery”&#xff0c; Computer-aided drug discovery has been around for decades, although the past few years have seen a tectonic shift towards embracing com…

马尔科夫链 Markov chain

马尔科夫链 1.实例参考文献 马尔科夫链(Markov chain)及其模型在机器学习中应用广泛&#xff0c;本文结合一些参考资料做一个总结。 注意的是&#xff0c;本文中提到的马尔科夫链&#xff0c;要与隐马尔科夫(HMM)作以区分。 1.实例 设某人的训练计划中有以下四项运动&#x…

通过VCP(VMware Certified Professional)认证

文章目录 小结要求考试证书参考 小结 通过VCP(VMware Certified Professional)认证&#xff0c;去年参加了VMware的培训&#xff0c;今年通过VMware Certified Professional (2V0-21.20)的考试&#xff0c;拿到证书。 要求 需要参加VMware的指定的培训&#xff0c;我在去年五…

【数据结构】 数组 array

一、什么是数组 连续内存空间&#xff0c;存储的一组相同类型的元素 二、常用操作 1.原理 access&#xff1a;通过索引直接读取元素的值 时间复杂度&#xff1a;O(1) search&#xff1a;遍历查找某个元素 时间复杂度&#xff1a;O(N) insert&#xff1a;插入位置后的元素依…

Generative AI 新世界 | 大语言模型(LLMs)在 Amazon SageMaker 上的动手实践

在上一篇《Generative AI 新世界&#xff1a;大型语言模型&#xff08;LLMs&#xff09;概述》中&#xff0c;我们一起探讨了大型语言模型的发展历史、语料来源、数据预处理流程策略、训练使用的网络架构、最新研究方向分析&#xff08;Amazon Titan、LLaMA、PaLM-E 等&#xf…

QT超市管理系统

QT超市管理系统 前言QT介绍.pro文件主文件&#xff08;main函数&#xff09;窗口函数&#xff08;mainwindow&#xff09;用户登录&#xff08;user_login&#xff09;超市系统数据库&#xff08;maketsql&#xff09;超市商品的增删改查(dlg_addmak)收款码界面(picture)结语 前…

亚马逊云科技探路可持续,数智创未来

气候变化是全人类的共同挑战。应对气候变化&#xff0c;事关永续发展&#xff0c;关乎人类前途命运。2020年中国在联合国大会上亚马逊云科技作出庄严承诺&#xff0c;要在2030年实现碳达峰&#xff0c;2060年实现碳中和&#xff0c;这是个全球性的规划&#xff0c;也是个庄严的…

Yolov8轻量化:MobileNetV3,轻量级骨架首选

1.轻量化网络简介 轻量化网络是指在保持模型性能的前提下,尽可能减小模型参数量和计算量的神经网络。这种网络通常被用于在移动设备等资源受限的场景中部署,以提高模型的实时性和运行效率。 轻量化网络的设计思路可以包括以下几个方面: 去除冗余层和参数:通过剪枝、蒸馏等技…