JS异步机制与Event Loop解析
搞懂 JS 异步内核从 Call Stack、Event Loop 到 DOM 渲染与 await 的假等待文章目录搞懂 JS 异步内核从 Call Stack、Event Loop 到 DOM 渲染与 await 的假等待一、 起点调用栈Call Stack为什么是“后进先出”1. 为什么必须是“后进先出”二、 核心引擎Event Loop 的四方协作架构1. 四大组件职责2. 事件循环的标准执行轮询Loop Rule三、 网络请求 pending 时JS 到底在做什么四、 破除迷思await 到底是不是在“等待”1. 用代码验证2. await 的底层本质Generator 协程 Promise 语法糖五、 进阶真相DOM 什么时候渲染是微任务还是宏任务1. 完整的 Loop 与 DOM 渲染管线2. 实验验证DOM 修改 vs 真实上屏六、 总结全景图七、 疑惑点讲解1、 出栈Yield 遇到 await 时引擎会保存当前函数的上下文变量、作用域然后直接将该函数弹出调用栈把控制权还给主线程。 形象比喻看书打书签核心拆解这句话里的 3 个动作1. “保存当前函数的上下文变量、作用域”2. “直接将该函数弹出调用栈”3. “把控制权还给主线程” 代码 调用栈动态变化图调用栈Call Stack的变化过程总结对于许多前端开发者来说JavaScript 的异步机制一直充满“貌似矛盾”的迷局单线程的 JavaScript 凭什么能一边处理耗时的网络请求一边保持页面流畅遇到fetch的pending状态时主线程不轮询、不死等它是怎么知道数据什么时候回来的写下await时函数看起来明明卡在那里等结果为什么浏览器没有死机DOM 渲染到底是在什么时候发生的它属于宏任务还是微任务本文将从最底层的调用栈Call Stack讲起层层剥离带你构建一个关于 JS 异步与浏览器渲染运行机制的完整知识拓扑。一、 起点调用栈Call Stack为什么是“后进先出”要理解异步首先要理解同步。JS 引擎用调用栈来追踪代码的执行位置。调用栈遵从LIFOLast In, First Out后进先出原则。这种数据结构并非为了异步设计而是所有编程语言处理“函数嵌套调用”的天然逻辑。1. 为什么必须是“后进先出”当函数A在执行过程中调用了函数BA必须暂停自身等待B执行完毕拿回返回值后才能继续往下走。functionfuncC(){console.log(C 执行);}functionfuncB(){funcC();// 暂停 B先去执行 Cconsole.log(B 执行);}functionfuncA(){funcB();// 暂停 A先去执行 Bconsole.log(A 执行);}funcA();压栈Push执行funcA→ \rightarrow→入栈funcA调用funcB→ \rightarrow→funcB入栈funcB调用funcC→ \rightarrow→funcC入栈。出栈PopfuncC最先执行完最先被弹出返回funcB继续执行funcB完成后弹出最后返回funcA执行完毕弹出。结论“后进先出”确保了内层函数执行完毕后能准确返回到外层函数的上下文断点处。二、 核心引擎Event Loop 的四方协作架构当遇到了耗时任务如定时器、网络请求单个调用栈就不够用了。此时运行环境浏览器/Node.js引入了另外三个角色配合工作---------------------------- | Call Stack (调用栈) | --------------------------- | (遇到异步任务委托出去) v ---------------------------- | Web APIs (网络线程/定时器) | --------------------------- | (底层事件就绪后放入) v ---------------------------------- | Microtask Queue (微任务队列) | --- 优先全额清空 ---------------------------------- | Macrotask Queue (宏任务队列) | --- 每次循环只取一个 ----------------------------------1. 四大组件职责Call Stack调用栈唯一负责执行 JS 代码的主线程。Web APIs浏览器后台线程池C 编写的多线程模块专门在后台跑耗时的异步任务网络请求、Timer 计时、DOM 事件监听。Microtask Queue微任务队列存放微任务如Promise.then、queueMicrotask、MutationObserver优先级最高。Macrotask Queue宏任务队列存放宏任务如script整体代码、setTimeout、setInterval、I/O。2. 事件循环的标准执行轮询Loop Rule执行主线同步代码这本身就是第一个宏任务直到调用栈清空。清空所有微任务检查微任务队列。若不为空依次弹出所有微任务推入调用栈执行。如果在执行微任务时又合成了新的微任务会接着加入当前队列并在本轮直接清空渲染更新检查Rendering Pipeline检查屏幕是否到了刷新周期如 60Hz 屏幕约 16.6ms。若需要进行 UI 绘制详见后文。取出一个宏任务从宏任务队列取出一个排队最久的任务推入调用栈执行。重复步骤 2如此周而复始。三、 网络请求 pending 时JS 到底在做什么当你发起fetch([https://api.example.com])Promise 处于pending状态时JS 主线程有没有在“等待”如果没等它怎么知道数据什么时候回来答案是JS 主线程完全没有管这回事。等待的是操作系统和网卡。[JS 主线程] [浏览器网络线程 / 操作系统内核] | | |--- 1. 发起 fetch 请求 ------------------| | |--- 2. 进行 DNS 解析/TCP 握手/发送 HTTP |--- 3. 拿到 Pending 的 Promise | | |--- 4. 线程休眠/依靠 OS epoll 机制监听 Socket |--- 5. 继续跑后续同步代码 (无卡顿) | | |--- 5. 网卡收到响应数据包 (触发硬件中断) | |--- 6. OS 唤醒网络线程状态变 Resolved |-- 7. 将 .then() 回调推入【微任务队列】--| | |--- 8. 主线程空闲从微任务队列取出回调执行非阻塞委托主线程执行fetch()向浏览器网络线程下达指令后立刻拿到一个初始状态为pending的 Promise 对象接着去跑后续的同步代码。内核级异步 I/O浏览器的网络线程发起请求后利用操作系统底层的多路复用机制如 Linux 的epoll、Mac 的kqueue挂起监听不需要占用 CPU 资源死循环检查。硬件中断与通知当数据包到达网卡时网卡触发硬件中断操作系统收到信号后通知浏览器网络线程。网络线程将 Promise 状态修改为Resolved并将对应的.then()回调函数推入微任务队列。微任务被调度主线程空闲时Event Loop 发现微任务队列有回调取出压入调用栈执行。四、 破除迷思await到底是不是在“等待”许多开发者会迷茫“写了await之后后面的代码确实没跑啊这难道不是卡住等待吗”这里的核心在于厘清“谁在等”与“谁没在等”。在等待的只有当前这个async函数本身它的局部执行上下文被暂停并压入后台。没在等待的JS 主线程它立刻跳出了这个函数去执行外部的其他同步代码或响应用户点击。1. 用代码验证asyncfunctiontest(){console.log(2. 进入 test 函数);// 遇到 await暂停 test 函数的后续执行让出主线程控制权awaitfetch(https://api.example.com);// 这里的代码在 await 完成前【确实不会执行】console.log(4. await 结束恢复 test 函数);}console.log(1. 主线程开始);test();// 调用 async 函数console.log(3. 主线程结束);控制台输出顺序1. 主线程开始2. 进入 test 函数3. 主线程结束← 关键点主线程根本没有等 await 返回直接跑完了外部代码4. await 结束恢复 test 函数(网络请求返回后)2.await的底层本质Generator 协程 Promise 语法糖当你写下asyncfunctionexample(){console.log(A);awaitdoSomething();console.log(B);}在 JS 引擎底层await后面的代码会被打包成Promise.then()中的微任务回调。这段代码会被等解为functionexample(){console.log(A);// 同步执行// await 相当于把【后面的所有代码】打包成了 .then() 中的微任务回调returndoSomething().then((){console.log(B);// 注册为微任务});}出栈Yield遇到await时引擎会保存当前函数的上下文变量、作用域然后直接将该函数弹出调用栈把控制权还给主线程。入队与恢复当doSomething()的 Promise 完成时之前打包的“后半段代码”被作为微任务推进队列被 Event Loop 重新压栈继续执行。工程避坑提醒串行 vs 并行理解了await会挂起当前函数就要避免将无依赖关系的请求写成连续await// ❌ 串行阻塞耗时 T1 T2constres1awaitfetch(/api/user);constres2awaitfetch(/api/posts);// ✅ 并行触发耗时 Max(T1, T2)const[res1,res2]awaitPromise.all([fetch(/api/user),fetch(/api/posts)]);五、 进阶真相DOM 什么时候渲染是微任务还是宏任务先给出一个确切结论DOM 渲染既不是微任务也不是宏任务它是处于微任务清空之后、下一个宏任务开始前的“独立浏览器管线阶段”。1. 完整的 Loop 与 DOM 渲染管线修改 DOM如div.style.color red只是同步更新了内存中的 DOM 树节点并不意味着屏幕像素发生了变化。真正的“绘制上屏Paint”由渲染引擎在特定节点统一触发很多人的误区在于以为浏览器里的一切都是任务队列里的回调函数。但实际上渲染Rendering是浏览器渲染引擎如 Blink/WebKit的绘制行为而不是 JS 引擎的任务。完整的 Event Loop 轮询生命周期是这样的------------------------------------------------------------- | 1. 执行一个宏任务 (如 script 全局代码, setTimeout 回调) | ------------------------------------------------------------- | v ------------------------------------------------------------- | 2. 清空所有的微任务 (Microtasks: Promise.then, await 后续代码) | ------------------------------------------------------------- | v ------------------------------------------------------------- | 3. 渲染更新检查 (Rendering Pipeline) | | - 检查屏幕是否需要刷新 (通常 60Hz 屏幕约 16.6ms 刷新一次) | | - 若需要刷新 | | a. 执行 requestAnimationFrame (rAF 回调) | | b. Refine/Layout (重新计算样式与布局) | | c. Paint (像素绘制到屏幕即真正肉眼可见的 DOM 渲染) | ------------------------------------------------------------- | v ------------------------------------------------------------- | 4. 取出下一个宏任务执行 (进入下一轮 Loop) | -------------------------------------------------------------关于requestAnimationFrame(rAF)它既不是宏任务也不是微任务而是专为动画设计的帧前回调精准运行在 Style 计算与 Layout 之前的步骤 1。检查屏幕是否需要刷新1修改了 DOM 或 CSS 样式 2注册了动画帧回调 调用了requestAnimationFrame(callback)。 3页面上有正在播放的动画 如 CSS Animation、Transition、video播放、canvas绘图等。4发生了用户交互 页面发生滚动Scroll、鼠标移动引发 :hover 状态改变等。2. 实验验证DOM 修改 vs 真实上屏document.body.style.backgroundred;// 1. 改变内存中的 DOMPromise.resolve().then((){// 2. 微任务阶段console.log(微任务执行中...);// 如果在此处加上 alert(画面变红了吗)// 答案是没有页面依然是白色的因为 Paint 渲染阶段还没到。});setTimeout((){// 3. 宏任务阶段console.log(下一个宏任务...);},0);从上述流程可以看出在微任务执行完毕前屏幕上绝对不会呈现最新的 DOM 变化。浏览器会在微任务清空后根据帧率控制通常 60fps集中将内存中所有的 DOM 变更一次性进行 Style/Layout/Paint 处理以此获得最高效的渲染性能。六、 总结全景图我们将整个 JS 异步与渲染运行机制整合为一张总结对比表阶段 / 概念发生时机与触发源执行特点与规则典型代表调用栈 (Call Stack)主线程同步执行LIFO 后进先出函数嵌套调用的天然上下文管理普通函数调用、new Promise()构造函数Web APIs浏览器后台 C/Rust 线程 pool不阻塞 JS 主线程由 OS 网卡中断或 Timer 硬件驱动fetch请求、setTimeout计时、DOM 事件微任务 (Microtask)语言层面的异步承诺回调宏任务结束或同步代码跑完后立刻一次性全额清空Promise.then、await后续代码、queueMicrotaskDOM 渲染 (Paint)浏览器渲染引擎 (Rendering Engine)发生在微任务清空后、下一个宏任务前负责绘制像素requestAnimationFrame、Style/Layout/Paint宏任务 (Macrotask)宿主环境浏览器/Node发起的任务每次 Event Loop 轮询只取出一个执行script整体代码、setTimeout、I/O一句话记住 JS 异步模型主线程永不阻塞异步耗时交给底层数据到期推入队列遇到 await 暂停函数出栈让路微任务清空后渲染上屏最后轮询下一个宏任务。七、 疑惑点讲解1、 出栈Yield 遇到 await 时引擎会保存当前函数的上下文变量、作用域然后直接将该函数弹出调用栈把控制权还给主线程。这句话确实是理解async/await最烧脑的地方因为它同时涉及到了内存保存和代码执行权的让出。我们可以把这句话拆成三个动作用一个“看书暂停”的比喻来彻底讲透它 形象比喻看书打书签假设你在看一本小说执行async函数看到第 10 页时发现这一页有一道谜题await fetch(...)你需要等朋友查完资料告诉你答案才能继续看第 11 页。这时候你有两种选择死等同步阻塞你双手死死按着书不放眼睛盯着门外啥也不干后面的所有工作全停下线程卡死。Yield 挂起await的做法夹书签保存上下文你在第 10 页夹了一张书签记下当前的笔记和思路保存当前的变量a, b和代码执行位置。把书放回书架弹出调用栈你把书合上放一旁双手腾出来了调用栈清空了该函数。去干别的控制权还给主线程你去烧水、扫地、接电话主线程去跑外部的其他同步代码。朋友把答案送来了Promise 完成你把书从书架拿下来翻到书签页恢复上下文压入调用栈继续读第 11 页。核心拆解这句话里的 3 个动作理解了比喻后我们再看这句话里的三个关键概念1. “保存当前函数的上下文变量、作用域”普通函数结束函数执行完弹栈时函数内部的变量会被直接销毁被垃圾回收。await暂停函数虽然暂停了但还没执行完。所以 JS 引擎会把这个函数当前的所有局部变量、this、以及“代码执行到了哪一行”拍一张快照存到堆内存里。等请求回来时能原封不动地恢复现场。2. “直接将该函数弹出调用栈”为什么一定要弹出调用栈Call Stack的规则是只有栈顶的任务才能执行而且栈顶任务不结束下面的任务全被卡住。如果不把async函数弹出去调用栈就被占着后续的任何代码包括用户的点击事件、UI 渲染都进不来页面就会无响应。所以引擎必须强制把当前async函数移出调用栈让调用栈空出来。3. “把控制权还给主线程”当async函数被弹出调用栈后主线程CPU就空闲了。它会顺着之前的调用链路继续向下执行调用该async函数后面的同步代码。 代码 调用栈动态变化图用一段简单代码来看看调用栈里具体发生了什么asyncfunctionfoo(){letx10;awaitfetch(...);// -- 执行到这里console.log(x);}functionmain(){foo();console.log(主线程继续跑);}main();调用栈Call Stack的变化过程时刻 1主线程启动 | | | main() | -- main 压栈 ------------ 时刻 2main 调用 foo | foo() | -- foo 压栈开始执行到 await | main() | ------------ 时刻 3遇到 await【关键时刻】 1. 引擎把 foo 的变量 (x10) 和执行进度存入堆内存打书签。 2. 把 foo() 弹出调用栈 | | | main() | -- foo 弹出后栈顶变回了 main() ------------ 时刻 4主线程恢复自由 main() 继续执行打印 主线程继续跑执行完毕后 main 也出栈。 | | -- 调用栈完全清空主线程可以去处理点击、渲染等其他事 ------------ 时刻 5很久以后fetch 完成了 网络线程把 foo() 的后半段放进微任务队列。 主线程从微任务队列取出恢复变量 (x10)重新压栈执行 | foo(恢复) | -- 重新压栈打印 x (10) ------------总结这句话的核心意思就是await并不是真的让 CPU 在那里死等而是把当前函数“打包存档”然后立刻踢出 CPU让 CPU 去跑别的代码等到异步有了结果再把存档解包重新放回 CPU 继续跑。 感谢阅读想了解更多 我的博客网站 | 记录思考分享干货 我的个人主页 | 关于我、开源项目

相关新闻

基于机器视觉的屏幕坏点与缺陷自动化检测系统全解析

基于机器视觉的屏幕坏点与缺陷自动化检测系统全解析

1. 项目概述:从“一点”到“零容忍”的屏幕质检革命在显示面板制造与终端产品质检领域,有一个看似微小却足以决定产品命运的问题:屏幕坏点。无论是手机、平板、笔记本电脑,还是电视、车载显示屏,一个不发光&#xff08…

2026/10/4 10:11:15 阅读更多 →
甲骨文Oracle在这个月一口气修了1449个漏洞

甲骨文Oracle在这个月一口气修了1449个漏洞

最新内容请微.信搜索公.众.号阅读 一场由AI引发的漏洞海啸: 如果你最近关注科技圈或网络安全界,可能会注意到一组令人惊叹的数据:微软在一个月内修补了 622 个安全漏洞,创下历史新高;而数据库巨头 Oracle&#xff08…

2026/10/4 22:15:35 阅读更多 →
构建AI大模型WebBridge:打通微信读书、Kimi与企业系统的数据桥梁

构建AI大模型WebBridge:打通微信读书、Kimi与企业系统的数据桥梁

1. 项目概述:为AI大模型装上“眼睛”和“手”最近在折腾AI应用开发的朋友,估计都遇到过同一个痛点:我们手里的大模型,比如GPT-4、Claude或者国内的各种大模型,虽然“能说会道”,但本质上还是个“盲人”。你…

2026/9/28 14:23:51 阅读更多 →

最新新闻

SpringBoot+Vue车间管理系统:从表设计到部署的完整毕设指南

SpringBoot+Vue车间管理系统:从表设计到部署的完整毕设指南

做Java Web毕设的同学,十个里有八个绕不开车间管理、仓储管理、生产管理这类题目。手头这套SpringBootVue的工厂车间管理系统平台,是我整理了完整源码、SQL脚本和接口文档之后沉淀下来的一套方案。它不是简单把增删改查堆在一起,而是按照真实…

2026/10/5 13:27:07 阅读更多 →
系统备份神器:整机镜像备份与快速恢复实战指南

系统备份神器:整机镜像备份与快速恢复实战指南

1. 为什么说它是“宝藏工具”:系统备份的核心价值拆解 1.1 备份这件事,大部分人都没做对 先说个实话:我见过太多人,电脑里存着几年的工作文档、几千张家庭照片、各种无法重新下载的工程文件,却从来没有一次完整的系统…

2026/10/5 13:27:07 阅读更多 →
Nacos注册中心与配置中心实战:从单机部署到高可用集群

Nacos注册中心与配置中心实战:从单机部署到高可用集群

先说个场景:你负责的微服务从 10 个涨到 60 个,每个服务还要配数据库地址、Redis 地址、各种开关。有一天你改了一个公共配置,挨个连服务器改 YAML,改到第 30 个的时候发现前面有一台改错了。这时候你大概能理解,为什么…

2026/10/5 13:27:07 阅读更多 →
线程池被阻塞任务打满?一次订单同步服务故障复盘

线程池被阻塞任务打满?一次订单同步服务故障复盘

那件事发生在周四下午,离业务高峰还有不到一小时。订单域里一个很普通的同步服务,接口耗时突然从 P99 50ms 涨到了 4 秒,错误率跟着往上跳。打开监控一看,机器 CPU 利用率只有 20% 左右,但线程池的活跃线程数已经打满 …

2026/10/5 13:27:07 阅读更多 →
ClusterIP 能 curl 通却 ping 不通:从 12 万条 iptables 规则到 ipvs 的自救

ClusterIP 能 curl 通却 ping 不通:从 12 万条 iptables 规则到 ipvs 的自救

title: ClusterIP 能 curl 通却 ping 不通:从 12 万条 iptables 规则到 ipvs 的自救 date: 2026-10-04 tags: [Java, Kubernetes, 微服务, 后端架构, 网络编程]去年 12 月的一个发布夜,我在公司值班。凌晨 00:23,订单中心的发布脚本把 order-…

2026/10/5 13:27:07 阅读更多 →
AI正念使用手册:四根支柱与实操指南,告别失控协作

AI正念使用手册:四根支柱与实操指南,告别失控协作

1. 从AI焦虑到AI正念:我为什么开始写这份使用手册 坦白说,我是个重度AI工具使用者。每天打开对话窗口的次数比打开朋友圈还频繁,但真正让我满意的对话结果,却少得可怜。多数时候是这样的:我抛出一个需求,AI…

2026/10/5 13:26:07 阅读更多 →

日新闻

马斯克杀回智能体战场,Grok 4.5万亿参数撑腰,Cursor接手数字白领项目:用TaoToken统一Key跑通多模型Agent工作流

马斯克杀回智能体战场,Grok 4.5万亿参数撑腰,Cursor接手数字白领项目:用TaoToken统一Key跑通多模型Agent工作流

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/5 0:00:22 阅读更多 →
AI编程工具插件机制详解:plugin.json配置与加载失败排查指南

AI编程工具插件机制详解:plugin.json配置与加载失败排查指南

1. 从“plugins”这个词说起:它到底在解决什么问题如果你最近在折腾 AI 编程工具,尤其是 Cursor、Codex CLI、Claude Code 这类带 CLI 的编辑器或命令行助手,那你大概率绕不开一个词——plugins。这个词本身不新鲜,从浏览器到 IDE…

2026/10/5 0:00:23 阅读更多 →
第26课:OpenClaw|日志审计与问题诊断:把日志链路改到 TaoToken 的排查清单

第26课:OpenClaw|日志审计与问题诊断:把日志链路改到 TaoToken 的排查清单

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/5 0:00:23 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/5 5:06:42 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/5 1:10:22 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/5 3:06:17 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 11:40:45 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 9:43:54 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 20:14:29 阅读更多 →