实现Vue-tiny-diff算法
前言
前面我们实现了基本的数据更新到视图渲染的逻辑,但是这种方式(innerHTML)是极其低效的, 因此,我们相应引入 dom 和 diff 算法, 数据到视图的过程变为:
state -> vdom -> dom
vNode 层
所谓 vNode, 就是一个表示 dom 结构的轻量对象
{tag, props, children;
}
为了方便创建, 引入创建一个创建节点的方法h
export function h(tag, props, children) {return {tag,props,children,};
}
我们需要修改 render 函数, 让其返回一个创建好的 vNode(vTree)
render(context) {return h('div',{id: 'id-1',class: 'class-1'},[h('p', null, String(context.value)), h('p', null, String(context.value))])},
接下来对返回的 vTree 挂载到真实的节点
let subTree = rootComponent.render(context);
mountElement(subTree, rootContainer);
mountElement 的实现逻辑
- 根据标签创建元素
- 更新属性
- 如果子节点为文本节点,直接创建, 若为数组,则递归创建
export function mountComponent(vnode, container) {const { tag, props, children } = vnode;// taglet ele = document.createElement(tag);// propsfor (const key in props) {if (Object.hasOwnProperty.call(props, key)) {const value = props[key];ele.setAttribute(key, value);}}/* children1. string2. object*/if (typeof children === "string") {const textNode = document.createTextNode(children);ele.appendChild(textNode);} else if (isArray(children)) {children.forEach((vnode) => {mountComponent(vnode, ele);});}container.appendChild(ele);
}function isArray(ele) {return typeof ele.sort === "function";
}
diff 算法
除了第一次挂载需要生成所有节点以外, 新的更新是在旧的基础上"缝缝补补", 这个差量更新的过程交给我们的 diff 算法
我们用一个变量isMounted
来将挂载和更新两阶段分开
export default function createApp(rootComponent) {return {mount(rootContainer) {let context = rootComponent.setup();let isMounted = false;let oldSubTree;effectWatch(() => {if (!isMounted) {isMounted = true;let subTree = (oldSubTree = rootComponent.render(context));mountElement(subTree, rootContainer);} else {let newSubTree = rootComponent.render(context);diff(newSubTree, oldSubTree);oldSubTree = newSubTree;}});},};
}
接下来我们就可以处理diff
的逻辑了, 需要分别对tag
,props
,children
的变更做处理,
因为 diff 的郭恒要对真实的 dom 节点进行操作, 在 mounted 过程中将 dom 渲染完成后,我们需要将其挂载到对应的 vNode 上
export function mountElement(vNode, container) {// ...let ele = (vNode.el = document.createElement(tag));// ...
}
- tag 变化的处理 ,这里用到了原生的
replaceWith
操作方法
if (newTree.tag !== oldTree.tag) {oldTree.el.replaceWith(document.createElement(newTree.tag));
}
- props 节点的处理
newTree.el = oldTree.el;
// props, 对比两个对象, 各自遍历一遍,找出各自不同的地方
let { props: newProps } = newTree;
let { props: oldProps } = oldTree;
if (newProps && oldProps) {Object.keys(newProps).forEach((key) => {// 同时存在,意味着需要更新节点let newVal = newProps[key];if (Object.hasOwnProperty.call(oldProps, key)) {let oldVal = oldProps[key];if (newVal !== oldVal) {newTree.el.setAttribute(key, newVal);}} else {// 旧的不存在, 创建newTree.el.setAttribute(key, newVal);}});
}
// 移除已不存在的旧节点
if (oldProps) {Object.keys(oldProps).forEach((key) => {if (!Object.hasOwnProperty.call(newProps, key)) {newTree.el.removeAttribute(key);}});
}
当然, 为了演示, 这里的处理过程比较简单,
- children 的处理
chilren 的处理相对比较麻烦,为了简化, 目前根据 children 的类型区分
即: newChildren[string, array] * oldChildren[array, string] = 4 种情况
前三种比较简单
let { children: oldChildren } = oldTree;
let { children: newChildren } = newTree;
if (typeof newChildren === "string") {if (typeof oldChildren === "string") {if (newChildren !== oldChildren) {newTree.el.textContent = newChildren;}} else if (isArray(oldChildren)) {newTree.el.textContent = newChildren;}
} else if (isArray(newChildren)) {if (typeof oldChildren === "string") {newTree.el.textContent = ``;mountElement(newTree, newTree.el);} else if (Array.isArray(oldChildren)) {// ...}
}
下面分析两者都是数组的情况, 为了简化, 只对节点的长度作处理,不处理相同长度内的节点移位操作
// 暴力解法: 只对节点的长度作处理,不处理相同长度内的节点移位操作
const length = Math.min(newChildren.length, oldChildren.length);
// 更新相同长度的部分
for (var index = 0; index < length; index++) {let newTree = newChildren[index];let oldTree = oldChildren[index];diff(newTree, oldTree);
}
// 创建
if (newChildren.length > oldChildren.length) {for (let index = length; index < newChildren.length; index++) {const newVNode = newChildren[index];mountElement(newVNode, newTree.el);}
}
// 删除
if (oldChildren.length > newChildren.length) {for (let index = length; index < oldChildren.length; index++) {const vNode = oldChildren[index];vNode.el.remove(); // 节点移除自身}
}
本文首发于个人 Github前端开发笔记,由于笔者能力有限,文章难免有疏漏之处,欢迎指正
相关文章:
实现Vue-tiny-diff算法
前言 前面我们实现了基本的数据更新到视图渲染的逻辑,但是这种方式(innerHTML)是极其低效的, 因此,我们相应引入 dom 和 diff 算法, 数据到视图的过程变为: state -> vdom -> dom vNode 层 所谓 vNode, 就是一个表示 dom 结构的轻量对象 {tag, props, children; }为…...

正则表达式测试工具
前言 正则表达式测试工具可供您输入正则表达式和测试文本,立即查看匹配结果. 下面是离线的HTML文件,同样可以提供相同的服务. 目录 使用说明 HTML代码 正则表达式的编写经验和方法 总结 使用说明 1.先将HTML代码存储成.html为后缀的文件; 2.然后用浏览器打开这个…...
Github 2024-08-02 开源项目日报 Top9
根据Github Trendings的统计,今日(2024-08-02统计)共有9个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Python项目4Go项目1C项目1Rust项目1Shell项目1Dockerfile项目1TypeScript项目1Dart项目1Docker-OSX: 在Docker容器中运行Mac OS X 创建周期:152…...

重生之我 学习【数据结构之顺序表(SeqList)】
⭐⭐⭐ 新老博友们,感谢各位的阅读观看 期末考试&假期调整暂时的停更了两个多月 没有写博客为大家分享优质内容 还容各位博友多多的理解 美丽的八月重生之我归来 继续为大家分享内容 你我共同加油 一起努力 ⭐⭐⭐ 数据结构将以顺序表、链表、栈区、队列、二叉树…...

前端day4-表单标签
<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>day4-表单</title> </head> <body&g…...
vue3-print-nb 表格打印分页,第一页有空白的情况出现解决方法(两种:一种原生,一种基于element表格)
第一种:基于element表格分页 <template><!-- element分组打印 --><div class"hello"><button v-print"printContent">打印</button><div id"printDiv"><p>工资统计表</p><p>…...
搜维尔科技:借助 Xsens中的远程人体录制功能,可以在任何位置以无限量同时捕捉无限数量演员的身体动作
借助 Xsens中的远程人体录制功能,可以在任何位置以无限量同时捕捉无限数量演员的身体动作 搜维尔科技:借助 Xsens中的远程人体录制功能,可以在任何位置以无限量同时捕捉无限数量演员的身体动作...

2024/08 近期关于AI的阅读和理解[笔记]
#Cohere 就像商业能力很强的云数仓公司 Snowflake 一样,Cohere 也采用了按需付费模式而不是按月或按年付费,而且它的付费模式很精细。Cohere 按照模型的不同能力,包括文本生成,文本总结,重新排名,文本分类…...

SmartEDA:解锁设计新境界,从工具到灵感的飞跃之旅!
在这个数据驱动的时代,每一次点击、每一次滑动都蕴含着无限的可能与洞察。然而,在众多数据分析工具中,SmartEDA不仅仅是一把解锁数据奥秘的钥匙,它更是一座桥梁,连接着冰冷的数据世界与创意无限的设计灵感之源。今天&a…...

解决Minizip压缩后解压时的头部错误问题
最近,在处理文件压缩的任务时,我遇到了一个有趣的问题。使用Minizip库进行文件压缩后,在解压过程中收到了一个关于"头部错误"的警告。尽管这个警告看似令人担忧,但解压操作最终仍然能够成功完成文件的解压。这引发了我的…...

数据库表水平分割和垂直分割?
0.数据库表的水平分割和垂直分割是两种常见的数据库优化技术,它们分别针对不同的场景和需求进行数据表的拆分。 1. 水平分割(Horizontal Splitting)主要是按照记录进行分割,即不同的记录被分开保存在不同的表中&#x…...

Linux源码阅读笔记18-插入模型及删除模块操作
基础知识 模块是一种向Linux内核添加设备驱动程序、文件系统及其他组件的有效方法,不需要编译新内核 优点 通过使用模块,内核发布者能够预先编译大量驱动程序,而不会致使内核映像的尺寸发生膨胀。内核开发者可以将实验性的代码打包到模块中&a…...

力扣面试经典算法150题:移除元素
移除元素 今日的题目依旧是力扣面试经典算法150题中数组相关的题目:移除元素 题目链接:https://leetcode.cn/problems/remove-element/description/?envTypestudy-plan-v2&envIdtop-interview-150 题目描述 给定一个排序数组 nums 和一个值 val&a…...
java关于前端传布尔值后端接收一直为false问题
前端传值: {"message":"我肚子疼","isChiefComplaint": true }后端接收对象结构体: public class SymptomInquiryDTO {private String message;private boolean isChiefComplaint; }结果后端接收到的值一直是false&…...

工具学习_CVE Binary Tool
1. 工具概述 CVE Binary Tool 是一个免费的开源工具,可帮助您使用国家漏洞数据库(NVD)常见漏洞和暴露(CVE)列表中的数据以及Redhat、开源漏洞数据库(OSV)、Gitlab咨询数据库(GAD&am…...

智观察 | 行业赛道里的AI大模型
“AI改变世界”被炒得热火朝天,结果就换来AI聊天? 实际上,在日常娱乐之下,AI正在暗暗“憋大招”,深入各行各业,发挥更专业的作用。 自动驾驶 最近“萝卜快跑”霸榜热搜长达一周,让无人驾…...
linux 进程 inode 信息获取
根据端口查找 ss -neltup | grep "$port"根据 pid 查找 ss -neltup | grep "pid$pid"根据 inode 查找 ss -neltup | grep "ino:$inode"根据pid查找进程打开的inode ls -al /proc/$pid/fd查看inode信息 cat /proc/$pid/net/tcp | grep $ino…...

计算机网络-网络层
负责在不同的网络之间转发数据包,基于数据包的 IP地址转发,每个数据包可以按照不同路径传输。网络层不负责丢包重传,以及数据包之间数据顺序的的问题。 网络设备 路由器工作在第三层:网络层,能看到网络层的地址&…...

机器学习:识别AI,GraphRAG,LoRA,线性变换,特征
1.AI识别 1.bitgrit 生成式 AI API 文档 生成式 AI 假图像检测 API 可用于以编程方式检测假图像(即由生成式 AI 创建的图像)。2.X Virality Prediction API 旨在预测推文的潜在病毒式传播力。https://bitgrit.net/api/docs/x_virality_prediction 2.Gr…...

阿里云SMS服务C++ SDK编译及调试关键点记录
一. 阿里云SMS服务开通及准备工作 在阿里云官网上完成这部分的工作 1. 申请资质 个人or企业 我这里是用的企业资质 2. 申请签名 企业资质认证成功后,会自动赠送一个用于测试的短信签名 也可以自己再进行申请,需要等待审核。 3. 申请短信模板 企…...

JavaSec-RCE
简介 RCE(Remote Code Execution),可以分为:命令注入(Command Injection)、代码注入(Code Injection) 代码注入 1.漏洞场景:Groovy代码注入 Groovy是一种基于JVM的动态语言,语法简洁,支持闭包、动态类型和Java互操作性,…...
椭圆曲线密码学(ECC)
一、ECC算法概述 椭圆曲线密码学(Elliptic Curve Cryptography)是基于椭圆曲线数学理论的公钥密码系统,由Neal Koblitz和Victor Miller在1985年独立提出。相比RSA,ECC在相同安全强度下密钥更短(256位ECC ≈ 3072位RSA…...

盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来
一、破局:PCB行业的时代之问 在数字经济蓬勃发展的浪潮中,PCB(印制电路板)作为 “电子产品之母”,其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透,PCB行业面临着前所未有的挑战与机遇。产品迭代…...

零基础设计模式——行为型模式 - 责任链模式
第四部分:行为型模式 - 责任链模式 (Chain of Responsibility Pattern) 欢迎来到行为型模式的学习!行为型模式关注对象之间的职责分配、算法封装和对象间的交互。我们将学习的第一个行为型模式是责任链模式。 核心思想:使多个对象都有机会处…...
数据库分批入库
今天在工作中,遇到一个问题,就是分批查询的时候,由于批次过大导致出现了一些问题,一下是问题描述和解决方案: 示例: // 假设已有数据列表 dataList 和 PreparedStatement pstmt int batchSize 1000; // …...

mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包
文章目录 现象:mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时,可能是因为以下几个原因:1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...

C++ Visual Studio 2017厂商给的源码没有.sln文件 易兆微芯片下载工具加开机动画下载。
1.先用Visual Studio 2017打开Yichip YC31xx loader.vcxproj,再用Visual Studio 2022打开。再保侟就有.sln文件了。 易兆微芯片下载工具加开机动画下载 ExtraDownloadFile1Info.\logo.bin|0|0|10D2000|0 MFC应用兼容CMD 在BOOL CYichipYC31xxloaderDlg::OnIni…...

视频行为标注工具BehaviLabel(源码+使用介绍+Windows.Exe版本)
前言: 最近在做行为检测相关的模型,用的是时空图卷积网络(STGCN),但原有kinetic-400数据集数据质量较低,需要进行细粒度的标注,同时粗略搜了下已有开源工具基本都集中于图像分割这块,…...

Web后端基础(基础知识)
BS架构:Browser/Server,浏览器/服务器架构模式。客户端只需要浏览器,应用程序的逻辑和数据都存储在服务端。 优点:维护方便缺点:体验一般 CS架构:Client/Server,客户端/服务器架构模式。需要单独…...

【Linux】Linux安装并配置RabbitMQ
目录 1. 安装 Erlang 2. 安装 RabbitMQ 2.1.添加 RabbitMQ 仓库 2.2.安装 RabbitMQ 3.配置 3.1.启动和管理服务 4. 访问管理界面 5.安装问题 6.修改密码 7.修改端口 7.1.找到文件 7.2.修改文件 1. 安装 Erlang 由于 RabbitMQ 是用 Erlang 编写的,需要先安…...