实现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. 申请短信模板 企…...

调用支付宝接口响应40004 SYSTEM_ERROR问题排查
在对接支付宝API的时候,遇到了一些问题,记录一下排查过程。 Body:{"datadigital_fincloud_generalsaas_face_certify_initialize_response":{"msg":"Business Failed","code":"40004","sub_msg…...

【人工智能】神经网络的优化器optimizer(二):Adagrad自适应学习率优化器
一.自适应梯度算法Adagrad概述 Adagrad(Adaptive Gradient Algorithm)是一种自适应学习率的优化算法,由Duchi等人在2011年提出。其核心思想是针对不同参数自动调整学习率,适合处理稀疏数据和不同参数梯度差异较大的场景。Adagrad通…...

【HarmonyOS 5.0】DevEco Testing:鸿蒙应用质量保障的终极武器
——全方位测试解决方案与代码实战 一、工具定位与核心能力 DevEco Testing是HarmonyOS官方推出的一体化测试平台,覆盖应用全生命周期测试需求,主要提供五大核心能力: 测试类型检测目标关键指标功能体验基…...

PPT|230页| 制造集团企业供应链端到端的数字化解决方案:从需求到结算的全链路业务闭环构建
制造业采购供应链管理是企业运营的核心环节,供应链协同管理在供应链上下游企业之间建立紧密的合作关系,通过信息共享、资源整合、业务协同等方式,实现供应链的全面管理和优化,提高供应链的效率和透明度,降低供应链的成…...

DBAPI如何优雅的获取单条数据
API如何优雅的获取单条数据 案例一 对于查询类API,查询的是单条数据,比如根据主键ID查询用户信息,sql如下: select id, name, age from user where id #{id}API默认返回的数据格式是多条的,如下: {&qu…...
SQL慢可能是触发了ring buffer
简介 最近在进行 postgresql 性能排查的时候,发现 PG 在某一个时间并行执行的 SQL 变得特别慢。最后通过监控监观察到并行发起得时间 buffers_alloc 就急速上升,且低水位伴随在整个慢 SQL,一直是 buferIO 的等待事件,此时也没有其他会话的争抢。SQL 虽然不是高效 SQL ,但…...

【Redis】笔记|第8节|大厂高并发缓存架构实战与优化
缓存架构 代码结构 代码详情 功能点: 多级缓存,先查本地缓存,再查Redis,最后才查数据库热点数据重建逻辑使用分布式锁,二次查询更新缓存采用读写锁提升性能采用Redis的发布订阅机制通知所有实例更新本地缓存适用读多…...
【FTP】ftp文件传输会丢包吗?批量几百个文件传输,有一些文件没有传输完整,如何解决?
FTP(File Transfer Protocol)本身是一个基于 TCP 的协议,理论上不会丢包。但 FTP 文件传输过程中仍可能出现文件不完整、丢失或损坏的情况,主要原因包括: ✅ 一、FTP传输可能“丢包”或文件不完整的原因 原因描述网络…...

Vue3 PC端 UI组件库我更推荐Naive UI
一、Vue3生态现状与UI库选择的重要性 随着Vue3的稳定发布和Composition API的广泛采用,前端开发者面临着UI组件库的重新选择。一个好的UI库不仅能提升开发效率,还能确保项目的长期可维护性。本文将对比三大主流Vue3 UI库(Naive UI、Element …...
TCP/IP 网络编程 | 服务端 客户端的封装
设计模式 文章目录 设计模式一、socket.h 接口(interface)二、socket.cpp 实现(implementation)三、server.cpp 使用封装(main 函数)四、client.cpp 使用封装(main 函数)五、退出方法…...