vue diff算法与虚拟dom知识整理(9) 手写patch递归子节点上树,用自己写的patch实现虚拟节点替换

news2025/7/1 7:04:13

上文 我们做到让一个文字虚拟节点上树 但子节点显然还 没有完成
那这次我们继续

递归的话 我们需要换个思路
我们将 src下的入口文件 index.js代码改成这样

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

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

const vnode = h("h1", {}, "你好");

patch( container, vnode)

我们还是像上次一样 先插入一个节点 但要换一种方式
createElement.js 代码修改如下

//创建节点
//函数接收两个参数  vnode  需要插入的虚拟节点   pivot  需要插入到哪一个元素之前
export default function(vnode) {
    //先用虚拟节点的sel  标签名选择器 创建一个真实的dom节点
    let domNode = document.createElement(vnode.sel);
    //如果  能判断到虚拟节点下的 text 文本属性  而且 拿不到children子节点  或者 children  子节点 没有长度
    if(vnode.text && (!vnode.children || !vnode.children.length)){
        //这个条件达到  表示  他的内容是一个文章  没有子节点
        //直接将虚拟节点的 text 文本内容 通过innerText写入到新节点中
        domNode.innerText = vnode.text;
        // 将新生成孤儿节点 domNode  保存在虚拟节点的elm属性上
        vnode.elm = domNode;
    //判断  如果  children 是个数组  说明 存在子节点
    }else if (Array.isArray(vnode.children) && vnode.children.length > 0) {
        //递归创建子节点

    }
    //将函数用虚拟节点创建的孤儿节点返回
    return vnode.elm
}

条件上的话 我们先加入如果children是个数组 且有长度的判断 但主要改的还是 得到孤儿节点后 不再直接在这个文件中插入了 我们将得到的孤儿节点保存在了虚拟节点的elm属性
最后 返回vnode的elm字段
那么 这里不插入节点 在哪里插入呢?
对 patch.js
patch.js 参考代码如下

import vnode from "./vnode";
import createElement from "./createElement";

export default function(oldVnode, newVnode) {
    //先判断oldVnode  第一个参数 是一个虚拟节点  还是一个真实的dom节点
    if(!oldVnode.sel) {
        //如果oldVnode中 拿不到sel  表示这是一个真实的节点  因为虚拟节点中  sel 代表标签类型选择器
        //那么 我们就要将虚拟节点转为真实的节点
        //这里 我们通过vnode  将真实节点转为真实节点
        /*
            第一个参数sel  标签选择器  我们通过tagName.toLowerCase() 获取dom节点的 标签类型
            第二个参数data  标签的数据  dom老节点肯定是没有的  我们直接{}
            第三个参数  children  子集集合   我们这里肯定是没有的  直接 []  或者  undefined
            第四个参数  text  文本内容 我们这里 没有直接undefined
            最后一个   elm  他上树的节点   我们直接给他自己  oldVnode
        */  
        oldVnode = vnode(oldVnode.tagName.toLowerCase(),{},[],undefined,oldVnode);
    }

    //判断  oldVnode  和  newVnode 是不是同一个节点
    //判断的条件是  sel  标签选择器  和  key  元素表示是否都相同
    if(oldVnode.sel == newVnode.sel && oldVnode.key == newVnode.key) {

    } else {
        //else 走进来 表示两者并不是同一个节点
        //那就执行 暴力拆除旧的  插入新的
        //先通过createElement  将虚拟节点变成一个个人节点
        let newVnodeElm = createElement(newVnode);
        oldVnode.elm.parentNode.insertBefore(newVnodeElm, oldVnode.elm);
    }
}

这里 我们定义一个 newVnodeElm 接一下 createElement的返回值 就是会返回vnode.elm 刚才生成的孤儿节点
然后 直接在这里 通过oldVnode.elm进行插入
我们运行项目
在这里插入图片描述
这样 我们就将逻辑进行了一个改造

改造是为了递归渲染子节点做铺垫 那么 铺垫都做好了 就下一步
先将
src下的 index.js 代码改成这样

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

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

const vnode = h("ul", {}, [
  h("li", {}, "1A"),
  h("li", {}, "2B"),
  h("li", {}, "3C"),
  h("li", {}, "4D")
]);

patch( container, vnode)

这里我们将vnode又改成了一个多层嵌套的虚拟节点 然后 关键文件就还是 createElement.js
然后 createElement.js 代码修改如下

//创建节点
//函数接收两个参数  vnode  需要插入的虚拟节点   pivot  需要插入到哪一个元素之前
export default function createElement(vnode) {
    //先用虚拟节点的sel  标签名选择器 创建一个真实的dom节点
    let domNode = document.createElement(vnode.sel);
    //如果  能判断到虚拟节点下的 text 文本属性  而且 拿不到children子节点  或者 children  子节点 没有长度
    if(vnode.text && (!vnode.children || !vnode.children.length)){
        //这个条件达到  表示  他的内容是一个文章  没有子节点
        //直接将虚拟节点的 text 文本内容 通过innerText写入到新节点中
        domNode.innerText = vnode.text;
    //判断  如果  children 是个数组  说明 存在子节点
    }else if (Array.isArray(vnode.children) && vnode.children.length > 0) {
        //循环遍历当前虚拟节点的所有 children 子节点
        vnode.children.map(item => {
            //通过调用自己createElement  将每个子集的虚拟节点也都变成真实的孤儿节点
            let chDom = createElement(item);
            //将孤儿节点 插入到上面创建的domNode上 因为  domNode 是通过当前调用createElement的虚拟节点创建的孤儿节点
            domNode.appendChild(chDom);
        })
    }
    // 将新生成孤儿节点 domNode  保存在虚拟节点的elm属性上
    vnode.elm = domNode;
    //将函数用虚拟节点创建的孤儿节点返回
    return vnode.elm
}

这里 我们判断 如果有子节点children就循环 children
一次一次 通过子节点去调用createElement 将字节点的虚拟dom变成孤儿节点 然后将孤儿节点插入到domNode下面
这个就比较奇妙 因为
例如 我们 div下 两个 h1 h1下面又有 带文本的三个p
那么 第一次 div虚拟节点进入 函数这次接到的vnode参数是div虚拟节点domNode是一个div虚拟节点创建的div孤儿节点
然后 我们进入到 vnode.children循环 然后 两个子h1虚拟节点 再去调用createElement
这里分为两个逻辑
首先 h1虚拟节点进入到 createElement 这次 接到的 vnode 就是h1虚拟节点
然后 domNode 就是h1 虚拟节点创建的一个 h1孤儿节点
然后 走到vnode.children循环 三个带文本的p虚拟节点进去createElement
然后 createElement 接到的 vnode 就是p虚拟节点
接到的 domNode 就是p 虚拟节点创建的一个p孤儿节点
然后就会走进vnode.text && (!vnode.children || !vnode.children.length)
文本被写入孤儿节点 然后 每一个 p的createElement 都将创建好的孤儿p节点返回
然后 还是在h1节点调用的createElement 中走到domNode.appendChild(chDom);
chDom是单个p调用createElement返回的p孤儿节点 然后被appendChild插入到domNode后面
domNode 是 h1 调用createElement时的domNode
然后 h1又返回孤儿节点
再到最外面div的createElement 也是一样的逻辑 domNode.appendChild(chDom);
chDom是h1调用createElement 返回的孤儿节点 domNode是当前div节点创建的孤儿节点
最终 div的createElement 返回 走到patch.js的
oldVnode.elm.parentNode.insertBefore(newVnodeElm, oldVnode.elm);
将整个节点挂上去

运行代码 我们的子节点就上去了
在这里插入图片描述
我们可以测试一下它有多级的时候

将src下的 index.js 代码更改如下

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

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

const vnode = h("ul", {}, [
  h("li", {}, "1A"),
  h("li", {}, "2B"),
  h("li", {}, "3C"),
  h("li", {}, [
    h("div", {}, "SRM"),
  ])
]);

patch( container, vnode)

跑起来也是没有任何问题
在这里插入图片描述
但 目前 patch.js 还少一个逻辑 我们一直说 插入新节点 删除旧节点

所以patch.js最后面加一句话

oldVnode.elm.parentNode.removeChild(oldVnode.elm);

在这里插入图片描述
然后 我们试着还原一下 之前写的 更改dom 的功能

我们将 src下的 index.js 代码更爱如下

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

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

const vnode = h("ul", {}, [
  h("li", {}, "1A"),
  h("li", {}, "2B"),
  h("li", {}, "3C"),
  h("li", {}, [
    h("div", {}, "SRM"),
  ])
]);

patch( container, vnode)

const btn = document.getElementById("btn");
const vnode1 = h("h1", {}, [
  h("p", {}, "你好"),
  h("p", {}, "我很好"),
]);
btn.onclick = function(){
  patch( vnode, vnode1)
}

这里 我们就有写上了 通过document.getElementById(“btn”)获取按钮节点 然后定义一个它的点击事件
里面的逻辑就是 用vnode1替换vnode

我们运行项目

在这里插入图片描述
然后点击一下 更改dom
在这里插入图片描述
可以看到 我们的节点替换就也好了

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

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

相关文章

Apache Zeppelin系列教程第七篇——运行paragraph的整个流程分析

Zeppelin运行paragraph的整个流程分析 前文分别讲述了,JdbcInterpreter、Interpreter、Zengine的流程,本文来主要串联起来paragraph的整个流程 前端 首先前端部分点运行的时候是通过websocket向后端发送请求的zeppelin-web/src/components/websocket/…

swp协议-1

Swp接口是UICC和CLF(非接前端)之间的面向比特流,点到点通信的协议。CLF是主设备(master),UICC是从设备(slave)。图SWP数据传输虽然是单线协议,但是是全双工数字传输。 1 …

电商项目之海量操作日志的实现

文章目录 1 问题背景2 前言3 思考4 解决思路5 交互6 工作原理7 伪代码实现7.1 安装并配置Canal Server7.2 Canal客户端拉取MQ消息7.3 Canal数据的转换7.4 定制自己的业务逻辑 1 问题背景 有时候客户做了某些操作却不认账,咱们又拿不出证据;有时候客户将账…

入参校验1

文章目录 一、简介1、快速失败(Fail Fast) 二、单字段类入参校验三、JSON实体类校验1、注解解析2、案例1、简单校验2、分组校验3、嵌套校验4、集合校验5、自定义校验 四、相关1、源码文件2、参考地址 一、简介 1、快速失败(Fail Fast) Spring Validation 默认会校验完所有字段…

GPT-4的免费使用方法分享(续)

GPT-4的免费使用方法分享_我爱OJ的博客-CSDN博客 在这篇博客里,我介绍了一些ChatGPT的一些使用方法,但可能有一定的缺陷,有的需要魔法,所以,今天我就来亲测一下,关于ChatGPT的一些免费使用技巧 目录 镜像…

代码随想录算法训练营第九天|KMP算法

记录一下KMP算法,本文摘录自《代码随想录》和部分b站视频帮你把KMP算法学个通透!(理论篇)_哔哩哔哩_bilibili最浅显易懂的 KMP 算法讲解_哔哩哔哩_bilibiliKMP字符串匹配算法2_哔哩哔哩_bilibili KMP算法 主要应用:字…

牛客小白月赛65

题目链接 牛客小白月赛65 A-牛牛去购物(枚举)B-牛牛写情书(字符串)C-牛牛排队伍(模拟)D-牛牛取石子(博弈)E-牛牛的构造(构造,思维) A-牛牛去购物…

怎么免费使用 ChatGpt,实用!

最近发现了一个可以免费、轻松使用 ChatGpt 的方法,随即做个记录,留着备忘,以后想用也能随时找到方法。 但是不保证该方法永远有效,仅当下有限,做个记录罢了。 因为我使用的是 windows 自带的浏览器 Microsoft Edge &a…

Android--刷机与adb

目录 一、Android设备启动流程 二、刷机模式介绍 三、Windows命令行 四、adb介绍与配置 五、常用的adb命令 一、Android设备启动流程 Android就是Linux内核(Kernel)Java虚拟机(JVM) Android设备启动就分为两个阶段: Linux启动 1.启动电源以及系统启动&#…

详解c++STL—容器list

目录 1、list基本概念 1.1、概念描述 1.2、结点的组成 1.3、list的优点 1.4、list的缺点 1.5、总结 2、list构造函数 2.1、功能描述 2.2、函数原型 2.3、示例 3、list赋值和交换 3.1、功能描述 3.2、函数原型 3.3、示例 4、list大小操作 4.1、功能描述 4.2、函…

ChatGPT的前世今生——混沌初开

目录 ChatGPT的前世今生——混沌初开ChatCPT简介ChatCPT是什么?ChatCPT的火爆程度ChatCPT火爆的原因1、功能强大,应用范围广泛2、训练数据量大,模型效果好3、优秀的商业模式 OpenAI公司公司创始团队 总结公众号文章链接参考链接: …

03C++类与对象之运算符重载

文章目录 C类与对象之运算符重载与const成员运算符重载赋值运算符重载运算符重载 日期类的实现与运算符重载赋值运算符重载比较类运算符的重载二元运算符-的重载前置和后置重载 总体实现代码const成员const的好处1.防止程序员犯错2.提高代码的复用性 const 成员与函数重载规则 …

Qt文件系统源码分析—第三篇QDir

深度 本文主要分析Windows平台,Mac、Linux暂不涉及 本文只分析到Win32 API/Windows Com组件/STL库函数层次,再下层代码不做探究 本文QT版本5.15.2 类关系图 QTemporaryFile继承QFile QFile、QSaveFile继承QFileDevice QFileDevice继承QIODevice Q…

由浅入深Netty基础知识IO相关

目录 1 stream vs channel2 IO 模型3 零拷贝3.1 传统 IO 问题3.2 NIO 优化 4 AIO4.1 文件 AIO4.2 守护线程4.3 网络 AIO 1 stream vs channel stream 不会自动缓冲数据,channel 会利用系统提供的发送缓冲区、接收缓冲区(更为底层)stream 仅支…

unity学习遇到的问题:解决VS不能加载Unity脚本,MonoBehaviour是灰色的

电脑出了点问题,然后就重装了,重装之后,从gitee上下载了原来的半截代码,结果发现里面的脚本运行出问题了,仔细一看,MonoBehaviour是灰色的,也就是说,加载不了unity的api了&#xff0…

目标检测复盘 --3. RCNN

RCNN的CNN部分使用AlexNet作为backbone来提取特征,Fast RCNN使用了VGG16来作为backboneRCNN将2000个框送入网络提取特征,Fast RCNN是将图像送入CNN来提取特征得到一个特征图将SS(Selective Search)算法获取的提议框映射到上面的特征图上,获取…

怎么通过ssh连上ipv6的服务器?阿里云怎么配置ipv6?wsl2怎么支持ipv6?

最近在研究ipv6,光调通环境居然让我折腾了好多回,现在终于通了 在这里提一句,IPV6和IPV4是两种东西,不要想着ipv6兼容ipv4,你就当它是全新的东西 1.前置条件 1.1我的电脑能访问ipv6 测试通过就代表你电脑可以访问ip…

Redis 哨兵模式的实现详解

文章目录 高可用(HA)哨兵模式概述哨兵的搭建伪集群 哨兵1. 复制sentinel.conf文件2. 修改sentinel.conf文件3. 新建sentinel26380.conf4. 启动并关联Redis集群5. 启动Sentinel集群6. 查看 Sentinel 信息7. 查看 Sentinel 配置文件 哨兵优化配置 高可用&…

【腾讯云Finops Crane集训营】降本增效神器Crane实战记录

本章目录 前言一、Crane是什么?Crane的主要功能?FinOps 是什么Prometheus是什么Grafana是什么 二、不得不面对的问题:云上资源效能挑战!三、云原生场景下的成本优化挑战?四、K8s原生能力的不足五、Crane智能调度助力成…

Linux命令之vim/vi

目录 vim/vi简介 vi/vim 的使用 操作实例 总结 vim/vi简介 所有的 Unix Like 系统都会内建 vi 文书编辑器,其他的文书编辑器则不一定会存在。但是目前我们使用比较多的是 vim 编辑器。Vim 是从 vi 发展出来的一个文本编辑器。代码补全、编译及错误跳转等方便编程…