中探:事件循环相关内容(因为不仅仅是初步认识,但也不至于是深入探讨,所以命名为“中探”)




下面内容写于 2022 年,文本描述过多,可能不适合有经验的人看。新的文章在 个人网站 中。

对了,说到事件循环,怎么可以离开这个最知名的视频呢!视频是英文的,但即使你听不懂,单纯看他的操作,我相信你也能够对事件循环有了清晰的认识!




🍕 涉及到的概念

  • 事件循环 (Event Loop)
  • 宏任务 (macrotask)
  • 宏任务队列 (macrotask queue)。在 WHATWG specification 中被简单地称为 task queue1
  • 微任务 (microtask)
  • 微任务队列 (microtask queue)
  • 执行栈, 或调用栈 (Call Stack)
  • 任务队列 (Task Queue, Event Queue), 我这里将 宏任务队列微任务队列 统称为 任务队列
  • 异步编程 (asynchronous programming)
  • 单线程 (single-thread)

🍕 通过一段代码, 来简单认识 Event Loop

我这里先做定义一个 异步任务源, 任务源可以分配任务, 异步任务源 就是专门分配 异步任务 的。这个想法来自 task-source | WHATWG。我这里不对定义做过多说明,直接看我的例子, 你应该就明白我想表达什么了:

比如 setTimeout(callback) 中的 setTimeout 是一个 异步任务源, 它的参数 callback 就是一个 异步任务, setTimeout 本身的执行是同步的, 只不过在它执行的过程中, 它创建一个 异步任务, 这个 异步任务 不会立马执行。

function test () {
  console.log('1') // f1
  setTimeout(fn, delay/* 假设是500ms */) // f2
  console.log('2') // f3
}
;(function main () {
  test()
})()

我专门画了一张图, 来简单描述一下过程

在这里插入图片描述

Event Loop

  1. 首先, main() 会进入 执行栈, 然后 执行栈 会自上而下执行该函数中的各条语句
  2. 在执行语句的过程中, 如果遇到形如同步函数, 比如 test(), 那么它会先等待其执行完毕, 也就是会将 test() 压入栈, 然后继续执行 test() 中的各条语句
  3. 在执行 f1 时, 执行栈 又遇到了同步函数 console.log, 于是它继续先等待其执行完毕, 也就是会先等屏幕输出 1, 然后才继续开始下一条语句
  4. 接着, 执行栈 遇到了 异步任务源, 也就是 setTimeout, 于是他会将 setTimeout 分配的 异步任务(包含 fn 和 delay) 送到某个区域, 这个区域我们先称其为 异步模块 (在浏览器环境中叫 APIs), 然后 执行栈 就会继续执行下一条语句了。注意此时 fn 并没有执行。

APIs 的执行由浏览器单独负责, 他和 JS 的单线程没有关系。记住: JS 是单线程的, 但浏览器是多线程的。 2

  1. 执行栈 接着执行, 然后会输出 2, 此时主函数执行完, 执行栈 中没有其他的任务需要执行了。
  2. 执行栈 为空时, 会让 Event Loop 从一个区域中取出新的任务执行, 这个区域我们称之为 任务队列, 任务队列 中的任务都是等待程序处理的任务, 这些任务的来源就是我们刚刚提到的 异步模块。因为此时时间还没有过去太久(不足500ms), 所以 任务队列 为空, 执行栈 也为空
  3. 异步模块 每过一段时间就会查看一下它维护的那些 异步任务, 经过大约 500ms(实际数值肯定大于500ms) 后, 异步模块 发现有一个 异步任务(fn) 可以执行了, 于是将这个 fn 发送给 任务队列, 此时 任务队列 不为空
  4. 执行栈 为空, 并且 任务队列 有任务在等待执行, 于是 Event Loop 从中取出任务(fn), 并发送到 执行栈 中执行, 于是 执行栈 继续执行, 并输出了 3

从上面的步骤可以看到, 一个重要的时间点就是 执行栈 为空, 并且这个时间点后, 相关操作都是由 Event Loop 处理的, Event Loop 负责调控整个流程。 当然 异步模块 中的内容是另外的一部分, 正因如此, 当 执行栈 中执行任务时, 异步模块 可以对异步任务进行计时。

总的来说, 执行栈 执行任务, 执行完后, 就会让 Event Loop任务队列 中取出新的任务送到 执行栈 中执行。注意, 当 执行栈 不为空时, 任务队列 中的任务是无法进入 执行栈 中执行的。

现在, 让我们了解两个新名词, 宏任务微任务, 前面的 异步任务 并不是常见的名词, 而是我自己在此处定义的名词, 异步任务 其实就是 宏任务微任务 的总称。

🍕 宏任务和微任务

宏任务微任务 是两类不同的 异步 任务。3

宏任务队列 的数据结构 不是队列, 而是 集合4
记住这个概念很重要, 因为队列是有序的, 而集合是无序的, 所以在 宏任务队列 中, 先到达的任务 不一定 会先执行。

微任务队列 的数据结构是 队列, 所以 微任务队列 中任务的执行一定是有序的。微任务队列 还有这么一个特点, 当 微任务队列 中的 微任务 开始执行时, 它可以继续添加新的 微任务微任务队列 中, 并且 微任务队列 一旦开始执行, 就会执行到 微任务队列 为空。换句话说, 如果不断的有新的 微任务 加入到 微任务队列 中来, 那么 宏任务 将不断的被阻塞, 无法执行。这种情况导致的最常见的后果就是页面无法响应你的鼠标或者滚轮, 因为与用户的交互是属于 宏任务为了处理无限递归的 微任务, 听说以前的 Nodejs 中, 会提供一个机制来限制最大的递归数量, 但我没有在官方文档中找到具体的内容。from1, from2

微任务队列 中的任务会一次性执行完, 带来的好处是它确保了每一个 微任务 之间的应用程序环境基本相同(没有鼠标坐标更改,没有新的网络数据等)。5

如果要给 微任务宏任务 定一个优先级, 那么你可以认为 微任务 的优先级更高。但我认为, 与其记住谁的优先级更高, 不如记住这么一句话: 每一个宏任务执行之前, 必须确保微任务队列为空6

下面给出已知的 宏任务微任务

  • 宏任务
    • setTimeout
    • setInterval
    • setImmediate (Node 独有)
    • requestAnimationFrame (浏览器独有)
    • I/O
    • UI rendering (浏览器独有)
  • 微任务
    • process.nextTick (Node 独有)
    • Promises (准确的说是 Promise.then() 中 then 的回调函数, 而不是 new promise(callback) 携带的回调函数)
    • Object.observe
    • MutationObserver
    • queueMicrotask

🍕 通过一段代码来理解宏任务和微任务

setTimeout(() => { // l-1
  console.log("宏任务: 计时任务1") // l-3
  Promise.resolve().then(() => { // l-4
    console.log("微任务1") // l-5
  })
}, 500);

setTimeout(() => { // l-2
  console.log("宏任务: 计时任务2") // l-6
  Promise.resolve().then(() => { // l-7
    console.log("微任务2") // l-8
  })
}, 500);

先讨论真正有用的, 也就是Node11之后版本和浏览器的版本, 下面以浏览器内核进行解释:

  • l-数字 代表某行代码
  • APIs 是浏览器中的一个机制, 详细的结果不清楚, 只知道一些异步API的处理, 都是由它进行处理的, 当异步函数执行完毕时, 也是由它负责发送给 任务队列7
  • 宏任务队列, 由宏任务组成的队列, 宏任务队列分为 计时器队列 (Expired Timer Callbacks, 即到期的setTimeout/setInterval)、IO事件队列(I/O Events)、即时队列 (Immediate Queue, 即 setImmediate)、关闭事件处理程序队列 (close Handlers)。8 9
  • 微任务队列, 由微任务组成的队列
  1. 首先, 浏览器自上而下的执行(执行的过程中 执行栈 中进行), 先执行 l-1, 发现是 setTimeout, 于是将它的参数(callback,delay)发送给 APIs
  2. 然后继续识别 l-2, 发现又是 setTimeout, 于是继续将它的参数发送给 APIs
  3. APIs 接收到 setTimeout 的内容后, 会进行计时, 当经过 delay(也就是500ms) 后, 会将 callback(也就是l-1的回调函数) 发送给 任务队列 中的 宏任务队列。这样的事情 APIs 干了两次(因为有两个 setTimeout)。
  4. (继2) 当 执行栈 为空时, Event Loop 会将 任务队列 中的任务发送到 执行栈 中执行。不过此时 任务队列 为空, 故什么都不执行
  5. 经过 delay 时间后, 两个计时器的回调函数将会被 APIs 发送给 宏任务队列
  6. 此时 执行栈 为空, 并且 微任务队列 为空, 宏任务队列 非空, 故可以将 宏任务队列 中的第一个 宏任务 送到 执行栈 中执行
  7. 执行栈 执行 l-1 的回调函数, 先执行 l-3, 此时直接输出 "宏任务: 计时任务1"
  8. 继续执行 l-4, 发现是 Promise, 于是将 then 的回调函数送到 APIs (反正是类似 APIs 的异步处理模块, 但该模块不属于 JS 的单线程范畴), 然后执行了 resolve 了, 于是 then 的回调函数被发送到了 微任务队列
  9. 执行栈 执行完 l-3l-4 后, 又为空了, 于是 Event Loop 继续查看 任务队列
  10. 此时的 任务队列 中, 微任务队列 有了新的 微任务, 故先执行 微任务, 也就是将 l-5 送入 执行栈 中执行, 此时输出 "微任务1"
  11. 执行完 l-5 后, 执行栈 为空, 微任务队列 为空, 于是 Event Loop 再从 宏任务队列 中取出一个 宏任务 送往 执行栈
  12. 后面的就和前面的重复(循环)了
  13. 先执行 l-6, 输出 "宏任务: 计时任务2"
  14. 在执行 l-7, 执行完后会 微任务队列 又增加了一个 微任务
  15. 执行栈 又为空, 继续查看 任务队列, 取出 微任务 送往 执行栈
  16. 执行 l-8, 输出 "微任务2"
  17. 结束, 任务队列 又为空, 不作任何操作, 等待异步模块继续发送某些 宏任务微任务任务队列 中, 比如用户突然点击了某个绑定了回调事件的按钮, 或者某个网络请求请求结束, 或者是之前设置的某些定时任务到了触发的时间了等等…

看完上面的过程, 我们其实可以发现, 打开网页时, 除了网页文档中的 script 脚本是直接送入 执行栈, 其他的 任务, 其实都是从 任务队列 中取出的了。或者更加简单一点, 我们可以直接认为, 文档中最开始的 script 其实就是在 任务队列 中的。可能是 宏任务, 也可能是 微任务。反正记住一点, 最先开始执行的肯定是 script 脚本中的内容, 其他的内容, 就都是从 任务队列 中取出的了, 而 任务队列 中的内容, 是由异步模块(比如 APIs, 其实我也就只知道一个 APIs 了)发送给我们的。

对于 宏任务微任务, 不需要在意谁先执行谁后执行, 只需要记住一点就可以了: 当一个 宏任务 想要执行时, 必须确保 微任务队列 为空。记住这一点后, 其他的都能够直接推理出来了, 比如 微任务队列 不为空时, 永远轮不到 宏任务 执行, 换句话说, 我们要小心使用 微任务队列, 不然会出现死循环的情况, 这也是为什么官网不建议我们用太多 queueMicrotask() 函数。

🍕 NodeJS11 之前的 Event Loop (不重要, 可忽略)

下面来谈一点 “过时” 的东西, 前面的分析, 在现在这个时间点(22.12.15)都是对的。而在之前, 也就是 NodeJS11版本之前(不包括11), node 和 浏览器的 Event Loop 机制是不一样的, 最大的区别就在于 宏任务微任务。前面已经说了,每一个 宏任务 执行时, 都要确保 微任务队列 为空, 这是新版本的标准。在此之前的版本有一点点不同,之前的版本所要求的的是 同一类宏任务队列 执行之前, 要确保 微任务队列为空。这个差异导致的结果就是,当存在两个 setTimeout 时, 会先执行完这两个宏任务, 然后再去执行微任务, 所以前面的代码, 用 node11 之前的版本运行时, 会是不一样的结果


node 10 vs 16

有关 nodejs 的 Event Loop 具体的流程图, 可以看下面这张图 10


11版本之前的流程图

在这个过程中, 还发现了一个 让人困惑 有意思的现象, 那就是当我们将两个 setTimeoutdelay 设置为 0 秒时, 输出的情况是不确定的, 有时候会出现 微任务1宏任务2 之前输出, 如图所示


nodejs10 different output

下面我想试着解释这么一种现象。

首先, 通过输出可以发现, 大多数情况下, 还是先输出两个宏任务, 然后才输出微任务, 这个很好理解, 当 宏任务队列 存在两个 setTimeout 时, 肯定会先执行完两个 setTimeout 后再去查看 微任务队列 (注意这是 NodeJS11 版本之前, 新版本不是这样的)。

那么什么情况下, 会出现先输出 宏任务1微任务1 呢?, 我认为关键就在于程序具体执行的细节中。上一段话中, 我们说了 宏任务队列 存在两个 setTimeout 时, 肯定会先执行完两个 setTimeout, 但实际运行时, 宏任务队列 中一定会存在两个 setTimeout 吗? 或者应该这么问, 当第一个 setTimeout 运行完后, 另外一个 setTimeout 真的存在 宏任务队列 中吗? 答案应该是不一定的, 让我逐帧来分析一下:

在开始分析之前, 容许我再重复一下上面的代码:

setTimeout(() => { // l-1
  console.log("宏任务: 计时任务1") // l-3
  Promise.resolve().then(() => { // l-4
    console.log("微任务1") // l-5
  })
}, 0)

setTimeout(() => { // l-2
  console.log("宏任务: 计时任务2") // l-6
  Promise.resolve().then(() => { // l-7
    console.log("微任务2") // l-8
  })
}, 0)
  1. 首先, l-1l-2 都会在 执行栈 中等待执行
  2. 执行栈 先执行 l-1, 此时会将 l-3,4,5 送到 异步模块 中。为方便描述, 记 l-3,4,5s1
  3. 执行栈 继续执行 l-2, 此时会将 l-6,7,8 送到 异步模块 中。 记 l-6,7,8s2

因为 异步模块 不属于 JS 单线程的范畴, 所以 异步模块 的内容和 执行栈 中的内容是可以并发进行的, 这就导致了一种分歧: 当 执行栈 为空时, 异步模块 可能还未将 s1 发送给 宏任务队列, 也可能已经将 s1 发送给 宏任务队列
理由: 我们设置的延迟时间是 0, 理论上 s1 被送往 异步模块 时, 异步模块 应该马上将其发送给 任务队列, 但实际上, 异步模块 应该会每隔一段时间, 才检查 s1 的延迟时间是否已经到期, 才决定是否将 s1 送往 宏任务队列
下面我们考虑的是情况是, 当 执行栈 为空时, 只有 s1 已经被送往 宏任务队列

  1. 执行栈s2 送往 异步模块 后, 执行栈 为空, 此时 宏任务队列 只有 s1, 微任务队列 为空, 于是 Event Loops1 送往 执行栈 执行
  2. 关键来了!, 存在这么一个时间节点, 执行栈 在执行 s1, 异步模块 在等待 s2 的时间到期, 任务队列 为空。 执行栈 先执行完 s1 时, l-4 (Promise callbacks) 会被送往了 异步模块, 并且 异步模块 还未将 s2 送往 宏任务队列。 也就是说, 此时的 异步模块 同时存在 s2l-4 (Promise callbacks), 并且 异步模块 会先将微任务 l-4 (Promise callbacks) 送往 微任务队列, 而 s2 还停留在 异步模块 中。

虽然我不清楚 异步模块 具体实现的源代码, 甚至都不敢保证 node 中存在 异步模块 这个机制, 但因为 微任务 是优于 宏任务 的, 所以, 当同时存在 0 秒延迟的 setTimeout 和微任务(Promise callbacks)时, 即使 setTimeout 是率先到达 异步模块 的, 我也认为微任务是有机会先于 setTimeout 被发送到 任务队列 的。

  1. 异步模块 先将 l-4 发送到 微任务队列, 此时, 执行栈 为空, s2 还未送往 宏任务队列, 微任务队列 中存在 l-4, 于是 Event Loop 就先将 l-4 送往 执行栈 中执行了, 从而导致了 微任务1 先于 宏任务2 输出。后面的分析就没有什么需要做笔记的了。

总的来说, 我的解释就是: 存在这么一个时间节点, 异步模块 中同时存在 宏任务2微任务1。并且, 虽然 宏任务2 先于 微任务1 进入 异步模块, 但 异步模块 还是可能先将 微任务1 发送到 任务队列, 从而导致了 微任务1 先于 宏任务2 执行。

其实, 我不确定上面的解释是否是正确的, 因为我不清楚 nodejs 的源代码是如何编写的, 更不确定 nodejs 是否真的存在一个 异步模块, 但因为浏览器存在一个 APIs, 所以我感觉 nodejs 可能也有一个 异步模块

下面我再用别人给出的 nodejs11版本之前的 Event Loop 图, 来解释一下:


关键的时间点1, setTimeout1 开始执行, 但 setTimeout2 还没有进入宏任务队列中


关键时间点2, 只要 微任务1 能够在 s2 还未执行时进入到队列中, 那么它就有很大概率先于 s2 执行

好了, 这一部分, 仅仅只是感觉有意思的, 所以想着纪录一下, 初次学习(说的就是我)不要 只想不做, 不然容易误入歧途, 最好休息一下, 放空放空大脑, 或者去看看大佬的文章, 再回头来思考一下。

🍕 总结

  • Event Loop 是一种机制, 它指示了异步任务任务之间的运行规则。
  • JS 的单线程, 体现在 执行栈 只有一个, 并且只有 执行栈 为空时, 才有机会将新的任务送入 执行栈 中执行。
  • 每一个宏任务执行之前, 必须确保 微任务队列 为空。两个 setTimeout 的回调函数, 属于两个宏任务。

🍕 参考资料

  • JS 的异步机制一探 - ByteEE
  • NodeJS Event Loop: JavaScript Event Loop vs Node JS Event Loop - Deepal Jayasekara(很优秀的系列文章, 这里有翻译版, 不过最新的一篇没有翻译 —— 有关Nodejs和浏览器的对比)
  • Difference between microtask and macrotask within an event loop context - stack overflow
  • Event Loop - WHATWG(想要更深入时必读)
  • what is an event loop in javascript, (不长, 可简单的认识什么是 Event Loop)
  • JavaScript Event Loop, (不是很长, 介绍的是 Event Loop, 图表更丰富一些)
  • Understanding the Event Loop, Callbacks, Promises, and Async/Await in JavaScript
  • event-loop
  • 带你了解事件循环机制(Event Loop)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数学建模-模糊性综合评价模型

中医药是中国传统文化的重要组成部分,凝聚了中华民族千百年来智慧的结晶。作为中医的发源地,中国政府一直致力于保护、发展和推广中医药,采取了一系列政策措施[]。目前,中国面临着老龄化日益加剧,老年人群中慢性疾病和…

keilC51明明已经定义的变量却报错未定义与期待asm

文章目录 keil_C51采用的是C89标准:C89变量定义标准:程序块: expected __asm:已经定义某个变量却报错未定义该变量:从汇编语言中看: keil_C51采用的是C89标准: C89变量定义标准: 1…

固态存储是未来|浅析SSD架构的演进与创新技术-1

常见的SSD架构中,包括了SSD控制器、NAND颗粒、DRAM颗粒三大组件,SSD控制器的固件需要兼顾坏块管理、ECC纠错、垃圾回收GC、磨损均衡WL、NAND die介质管理、缓存交互等等。 随着时代的发展,SSD架构,也不断有新的挑战和需求。基于小…

基于YOLOv8/YOLOv7/YOLOv6/YOLOv5的交通标志识别系统详解(深度学习模型+UI界面代码+训练数据集)

摘要:本篇博客详细介绍了利用深度学习构建交通标志识别系统的过程,并提供了完整的实现代码。该系统采用了先进的YOLOv8算法,并与YOLOv7、YOLOv6、YOLOv5等早期版本进行了性能评估对比,分析了性能指标如mAP、F1 Score等。文章深入探…

stm32f103c8t6学习笔记(学习B站up江科大自化协)-USART串口-硬件部分

通信协议简介 USART串口 硬件电路 通信距离: TTL和RS232通信距离只有几十米 RS485电平通信距离可达上千米 应用场景: TTL用于单片机这种低压小型设备 RS232一般在大型机器上使用,由于环境比较恶劣静电干扰比较大,所以电压电平比…

javaEE13(网站第8章两个课后题)

1、对“jspservletjavabean实现分页查询”功能做如下补充: (1)记录批量删除:每个记录前添加复选框,点击批量删除,删除选中记录。 增加跳转到任意页功能。用户可改变每页记录条数。 页面&am…

wait 和 notify方法

目录 1.1 wait()方法 wait 做的事情: wait 结束等待的条件: 1.2 notify()方法 1.3notifyAll方法 1.4wait()和sleep()对比 由于线程之间是抢占式执行的, 因此线程之间执行的先后顺序难以预知. 但是实际开发中有时候我们希望合理的协调多个线程之间的执行先后顺序. 完成这个协调…

Python 的练手项目有哪些值得推荐?

Python 是一种强大的编程语言,有许多值得推荐的练手项目。以下是一些例子: 数据分析:利用 Python 的数据分析库(如 pandas 和 NumPy)处理和分析数据。你可以尝试对数据进行清洗、可视化,或者构建简单的预测…

4-LINUX--文件操作命令

一、文件查看命令 1. cat 1.1 查看文件内容,示例如下: 1.2 合并文件,示例如下: 1.3 往文件中写入数据, Ctrld 是结束输入,示例如下: 2. more 当一个文件的内容超过一个屏幕能显示的行数…

PCL点云处理之最小二乘球面点云拟合(直接拟合法,完整实验)(二百三十一)

PCL点云处理之最小二乘球面点云拟合(直接拟合法,完整实验)(二百三十一) 一、算法介绍二、算法实现1.代码2.结果一、算法介绍 现在假设有这样一群点云,它们分布在一个球的表面,可能并不严格,带有些许噪声,此时我们需要计算球心坐标和球的半径。(这里提供完整的实验过…

[leetcode] 43. 字符串相乘

文章目录 题目描述解题方法相乘累加java代码复杂度分析 题目描述 给定两个以字符串形式表示的非负整数 num1 和 num2,返回 num1 和 num2 的乘积,它们的乘积也表示为字符串形式。 注意:不能使用任何内置的 BigInteger 库或直接将输入转换为整…

怎么免费下载无水印视频素材?赶快收藏这六个网站。

今天来教大家怎么下载无水印视频素材,其中一些是免费的,并且可以在商业项目中使用,这些网站都是无水印视频素材,可以放心使用。 蛙学网: 网站的内容非常丰富多彩,包括风景,夜景,食物…

怎么判断你的模型是好是坏?模型性能评估指标大全!

模型性能评估指标,大家一定不陌生!很多小伙伴们都说难,但是它真的很重要很重要很重要!它会对我们的模型有很多的指导,也会给我们真正做模型的时候提供一些指导性的思想,不然我们看到别人的东西只能跟着人家…

阳光保险MySQL数据库平稳迁移OceanBase,稳定运营超700天

作者简介: 车东兴:于阳光保险就职,深耕保险行业的 IT 领域长达12 年,对保险领域的基础架构实践有深刻的理解与掌握。熟悉多款数据库,具有丰富的数据库运维经验。 王华城:于阳光保险就职,10多年一…

Eclipse安装springboot

Eclipse免费,套件丰富,代码开源,功能强大…推荐! 1 下载eclipse: https://www.eclipse.org/downloads/download.php?file/technology/epp/downloads/release/2023-12/R/eclipse-jee-2023-12-R-win32-x86_64.zip 2 安装Spring框…

【Vue3】什么是路由?Vue中的路由基本切换~

💗💗💗欢迎来到我的博客,你将找到有关如何使用技术解决问题的文章,也会找到某个技术的学习路线。无论你是何种职业,我都希望我的博客对你有所帮助。最后不要忘记订阅我的博客以获取最新文章,也欢…

AI写真变现项目丨超级训练营SOP手册

出品方: 吴东子团队 x AI破局俱乐部 以下只是该SOP手册的部分介绍,AI写真变现项目上手到变现全流程,需要完整手册的可以dd我。 AI写真 首先什么是AI写真,顾名思义的话可以说成是用AI生成写真照,我们先暂且这么理解&am…

学习数据节构和算法的第15天

单链表的实现 链表的基本结构 #pragma once #include<stdio.h> typedf int SLTDataType; typedy struct SListNode {SLTDataType data;struct SListNode*next; }SLTNode;void Slisprint(SLTNode*phead);打印链表 #include<stdio.h> void SListPrint(SLTNode*phe…

springboot+nacos使用

依赖 nacos服务发现和注册的依赖 <dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency><dependency><groupId>com.alibaba.cloud</g…

【掌握版本控制:Git 入门与实践指南】操作仓库文件|分支管理

&#x1f3ac;慕斯主页&#xff1a;修仙—别有洞天 ♈️今日夜电波&#xff1a;泥中に咲く—ウォルピスカーター 0:34━━━━━━️&#x1f49f;──────── 4:46 &#x1f504; ◀️ ⏸ ▶…