第一周03天 事件循环与宏任务 / 微任务

两小时学习讲义:事件循环与宏任务 / 微任务

配套《学习计划.md》使用,本讲义把每个知识点打碎到颗粒级 ,并逐点给出类比 + 例子 + 真实开发场景 。 学习方法建议:先猜后跑。遇到任何异步代码,先在纸上写下"每一行的宏/微任务归属和输出顺序",再运行验证。 全程约 120 分钟,与学习计划的时间轴一一对应。


⏱️ 时间轴总览

时间段 环节 本讲义位置
20:00--20:10 🔁 回顾与预习 第 1 段
20:10--20:25 📚 知识块①:单线程 + 调用栈 + Web API 第 2 段
20:25--20:40 📚 知识块②:宏任务 vs 微任务 第 3 段
20:40--20:50 📚 知识块③:一次 tick + 推演 第 4 段
20:50--21:00 📝 费曼复述 第 5 段
21:00--21:20 🛠️ 练习①:event-loop-basic 第 6 段
21:20--21:40 🛠️ 练习②③:event-loop-advanced + jsv9000 第 7 段
21:40--21:50 💭 思考提高题 第 8 段
21:50--22:00 ✅ 测验 第 9 段

第 1 段(10min)回顾与预习:接住昨天的线

1.1 先接昨日(原型链)的线

昨天你学了对象怎么找属性 (沿原型链上溯)。今天换一个维度:代码怎么排队执行

把前三天的知识串成一句话,你已经有了 JS 的三张地图:

kotlin 复制代码
变量 → 看定义处(闭包,周一)
this  → 看调用处(四种绑定,周二)
属性 → 看原型链(上溯查找,周三)
代码执行顺序 → 看事件循环(今天:谁排队、谁插队、谁先走)

1.2 先凭直觉回答 4 个小问题(不用对,先写下答案)

js 复制代码
// 问题 1
console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => console.log('3'));
console.log('4');
// 输出顺序?为什么 Promise 的 3 反而在 setTimeout 的 2 前面?

// 问题 2:一个死循环会怎样
while (true) {}          // 页面上会发生什么?setTimeout 还能执行吗?

// 问题 3:async 是"一调用就全异步"吗?
async function f() { console.log('A'); await Promise.resolve(); console.log('B'); }
console.log('C');
f();
console.log('D');
// 输出顺序?

// 问题 4:下面哪个会"永远轮不到"?
function loop() { Promise.resolve().then(loop); }
loop();
setTimeout(() => console.log('轮不到我'), 0);   // 会执行吗?

学完第 2、3、4 段再回来对照。第 1 段只做一件事:记住画面------JS 单线程,但靠"排队 + 轮转"假装多线程


第 2 段(15min)知识块①:单线程模型 + 调用栈 + Web API

2.1 为什么 JS 必须是单线程

知识点(细化)

  • JS 从出生起就是单线程:同一时刻只执行一段代码。
  • 原因:JS 是浏览器的脚本语言,主要职责是操作 DOM 。如果两个线程同时改一个 DOM 节点,渲染结果就无法确定。多线程 + 加锁的方案太复杂、易出错,所以干脆单线程------用"不可能出问题"换"性能天花板"
  • 那页面为什么看起来不卡?因为异步不阻塞:要等的活儿(定时、网络、事件)外包给浏览器,JS 自己不等。

类比(单窗口银行 / 单主厨餐厅) :银行只有一个窗口,同一时刻只有一位客户办业务。其他客户"等待"时不需要占用柜员------他们在排队,柜员忙完就叫下一位。JS 的"等"也一样:等待不占主线程,占的是队列里的位置。

真实场景 :你写 fetch(url).then(...) 时,主线程没有干等网络返回,而是继续执行后面的代码。等数据到了,浏览器把回调投进队列,主线程有空再执行------这就是"异步不阻塞"的直观体现。

2.2 四个角色:调用栈 / Web API / 任务队列 / 事件循环

知识点(细化)

角色 是什么 关键点
调用栈 Call Stack 记录当前正在执行的函数,后进先出(LIFO) 栈空 = 主线程空闲,才取任务
Web API 浏览器提供的异步能力:setTimeoutfetchaddEventListenerrequestAnimationFrame... JS 调用后立即返回,不等待
任务队列 Task Queue 存放"到时间/到数据"的回调,排队等待 先进先出(FIFO)
事件循环 Event Loop 不断检查:调用栈空了吗?空就取队列头执行 这是"轮转"的发动机

例子:四个角色怎么配合

js 复制代码
console.log('开始');            // ① 进栈 → 同步执行 → 输出'开始' → 出栈
setTimeout(() => console.log('定时器回调'), 0); // ② 调用 Web API → 立即返回,主线程不等
console.log('结束');            // ③ 同步执行 → 输出'结束'
// ④ 0ms 后,浏览器把回调投进任务队列
// ⑤ 事件循环发现栈空了 → 取出回调 → 执行 → 输出'定时器回调'
// 输出:开始 → 结束 → 定时器回调

类比(厨房流水线):主厨(主线程)只有一个灶台(调用栈)。需要"烤 10 分钟"的菜(异步任务),交给烤箱(Web API),主厨不守在旁边,继续做下一道。烤箱"叮"(完成)后,把菜放进"候菜台"(任务队列)。领班(事件循环)看主厨手头空了,就从候菜台叫下一个。

真实场景(为什么页面会"无响应") :如果有一段同步 代码占用主线程太久(比如巨大循环、卡住的递归、死循环),调用栈一直不为空,事件循环就无法取任务------setTimeout 到点了也排不进、fetch 回来了也没人处理、用户点击没反应。这就是"页面卡死"的本质:不是异步任务太多,而是同步代码把主线程堵死了。

2.3 调用栈:再近一点看

知识点(细化)

  • 每调用一个函数,压入一个"帧"(记录参数、局部变量、返回位置);函数返回时弹出。
  • 栈是 LIFO:后进先出,符合"函数里调函数,先执行内层再返回外层"。
  • 栈溢出(stack overflow):无限递归时帧无限增长,栈爆了。

例子

js 复制代码
function a() { b(); }
function b() { c(); }
function c() { console.log('内层执行'); }
a();
// 栈的变化:a 进栈 → b 进栈 → c 进栈 → c 出栈 → b 出栈 → a 出栈

// 栈溢出
function boom() { boom(); }   // Uncaught RangeError: Maximum call stack size exceeded

真实场景浏览器开发者工具 → Sources → Call Stack 面板在断点调试时展示的正是这张栈;"Maximum call stack size exceeded"是递归写崩时的经典报错。记牢:栈非空,队列里的任务就别想执行。

2.4 任务队列:也分优先级?

知识点(细化)

  • 任务队列其实不止一条:宏任务队列 + 微任务队列(第 3 段重点)。
  • 事件循环每轮只从宏任务队列取一个,但微任务要"清空"。
  • 也就是说:任务不是简单 FIFO 混排,宏任务之间插着一整轮微任务。

这一段先记住结论,第 3 段马上讲"为什么"。


第 3 段(15min)知识块②:宏任务 vs 微任务

3.1 两张清单(背下来)

知识点(细化)

类型 包括(浏览器) 特点
宏任务 macrotask setTimeoutsetInterval、I/O、UI 事件(click 等)、requestAnimationFrame、整体 script;Node 另有 setImmediate 每轮取一个
微任务 microtask Promise.then/catch/finallyawait 之后的代码、queueMicrotaskMutationObserver;Node 另有 process.nextTick(优先级更高) 每轮清空到空

类比(两种排队) :宏任务队列是"普通叫号",一次叫一位;微任务队列是"VIP 通道",并且规矩是------普通客户办完业务离开窗口后,下一号普通客户进来之前,所有 VIP 必须全部办完。清空了,普通客户才叫号。

3.2 铁律:每个宏任务执行完,清空整个微任务队列

知识点(细化)

  • 执行顺序是:一个宏任务 → 清空所有微任务 → 下一个宏任务 → 清空所有微任务 → ...
  • 关键细节:"清空"是清到空为止 ------清微任务的过程中新产生的微任务也要继续执行,直到队列真的空了。
  • 因此任何微任务都必然先于下一个宏任务

例子:先猜后跑

js 复制代码
setTimeout(() => console.log('宏任务A'), 0);
Promise.resolve().then(() => console.log('微任务1'));
Promise.resolve().then(() => console.log('微任务2'));
// 输出:微任务1 → 微任务2 → 宏任务A

例子:微任务里再生微任务(清空式)

js 复制代码
setTimeout(() => console.log('宏任务'), 0);
Promise.resolve()
  .then(() => { console.log('微1'); return Promise.resolve(); })
  .then(() => console.log('微2'));
// 输出:微1 → 微2 → 宏任务(微2 是清空过程中新产生的,也要执行完)

3.3 为什么微任务必须先进?(这是设计,不是巧合)

知识点(细化)

  • 规范规定:一个宏任务执行完,必须清空微任务队列,才能继续下一个宏任务。
  • 目的:保证 Promise 的时序一致性.then 的回调应该尽快、稳定地执行,不能被其他宏任务"插队"打断。否则:
js 复制代码
// 如果 .then 是宏任务,会发生什么?
fetch('/data')
  .then(() => updateUI());   // 假设是宏任务
setTimeout(() => clearUI(), 0);  // 可能插在中间,把刚更新的 UI 清了------顺序就乱了
  • await 之后的代码本质就是 .then 的语法糖,所以也属于微任务

类比(餐厅出菜):后厨规定"这道菜出锅后,必须先把配菜/酱料(微任务)摆好,再出下一道主菜(宏任务)"------为了保证一桌菜的完整性,不允许别的桌插队打乱摆盘。

真实场景 :Vue3 的响应式更新用微任务做批处理nextTick 底层就是 Promise.then)------连续改 100 个状态,只在微任务里渲染一次;如果混进宏任务,可能被渲染或用户事件插入,产生闪烁。

3.4 微任务风暴:清空式带来的"副作用"

知识点(细化)

  • 既然清空到空,那如果微任务里不断追加微任务,队列就永远空不了------宏任务和渲染会被活活"饿死"。
js 复制代码
function loop() { Promise.resolve().then(loop); }
loop();
setTimeout(() => console.log('永远轮不到我'), 0);  // 可能永远不执行

类比(插队的 VIP 没完没了):VIP 一个接一个插进来,普通客户永远轮不到------业务就瘫痪了。

真实场景 :某个监听器在微任务里又触发新的状态更新、又排微任务,就可能形成风暴卡死页面。性能调优时,"微任务里不要无限再生微任务"是红线。


第 4 段(10min)知识块③:一次 tick 的完整过程 + 代码推演

4.1 一次 tick 四步(背下来)

知识点(细化)

复制代码
一次 tick:
① 从宏任务队列取【一个】宏任务执行(首次加载的整体同步脚本也算一个宏任务)
② 执行期间产生的微任务全部入微任务队列
③ 宏任务执行完,清空【整个】微任务队列(新产生的也算)
④ 必要时渲染(先跑 requestAnimationFrame 回调,再更新布局与绘制)
⑤ 回到 ① 取下一个宏任务

关键点渲染发生在"微任务清空之后、下一个宏任务之前"。这决定了"改 DOM + 读布局"类代码的时序(第 8 段思考题③)。

4.2 setTimeout(0) 真的 0ms 吗?------不是

知识点(细化)

  • setTimeout(fn, 0) 的含义是"尽快把回调排进队列",不是"0ms 后立即执行"。
  • 三个让它变慢的因素:
    1. 最小延迟钳制 :Chrome 对嵌套层级 ≥ 5 层的定时器,最短延迟提升到约 4ms(防抖/节流递归写多层的坑)。
    2. 主线程繁忙:前面排队的同步代码和任务没跑完,回调就得等着。
    3. 后台标签页节流 :页面不可见时,浏览器会把定时器最小延迟提到 1 秒左右以省电。

例子

js 复制代码
let n = 0;
function nested() { if (n < 6) { n++; setTimeout(nested, 0); } }
nested();  // Chrome 里第 5 层起,每次实际等待 ≥ 4ms

类比(叫号)setTimeout(0) 是"排到 0 号优先位",但窗口前还有人在办业务、前面还有号没叫完,你还是要等。

4.3 手把手推演经典题(把"推演表格"练会)

知识点(细化) :掌握一张"推演表"------四列:栈 / 微任务队列 / 宏任务队列 / 输出。一行行扫代码,把每一步写进去。

例题

js 复制代码
console.log('1');                          // 同步 → 输出 1
setTimeout(() => console.log('2'), 0);     // 宏任务 → 进宏任务队列
Promise.resolve().then(() => console.log('3')); // 微任务 → 进微任务队列
console.log('4');                          // 同步 → 输出 4
// 同步执行完 → 清空微任务:输出 3 → 渲染(可能) → 取下一个宏任务:输出 2
// 最终:1 4 3 2

推演表(写给自己看的样子)

步骤 微任务队列 宏任务队列 输出
console.log('1') --- --- --- 1
setTimeout(...) --- --- 2
.then(3) --- 3 2
console.log('4') --- 3 2 4
清空微任务 →3 2 3
取宏任务 →2 2

推演表是今天的核心技能。练习①会让你多画几张,画熟了,任何异步题都是填表题。

4.4 进阶推演:async/await 拆解

知识点(细化)

  • async function函数体同步执行到第一个 await 之前,然后"让出"。
  • await 之后的代码 → 被包成微任务(等价 .then)。
  • 调用 f() 本身不阻塞后续同步代码。

例子:先猜后跑

js 复制代码
async function f() {
  console.log('A');            // 同步(await 之前)
  await Promise.resolve();     // 让出,后续变微任务
  console.log('B');            // 微任务
}
console.log('C');              // 同步
f();                           // 同步执行到 await → 输出 A,然后让出
console.log('D');              // 同步
setTimeout(() => console.log('E'), 0);  // 宏任务
// 输出:C A D B E

"async 函数不是全异步" ------A 是同步打的,B 才是微任务。忘了这一点,推演必错。


第 5 段(10min)费曼复述:讲给"零基础同学"听

5.1 复述提纲(出声讲一遍,卡壳处回去重看)

  1. JS 单线程怎么还不卡? ------ 等待外包给 Web API,自己只做"取任务 → 执行"。
  2. 两条队伍怎么排? ------ 宏任务一次叫一个;但每叫完一个,微任务必须全部清空才叫下一个。
  3. 为什么微任务先进? ------ 这是设计:保证 Promise 的 .then 不被其他宏任务插队,时序才稳定。
  4. 一次 tick 四步? ------ 执行宏任务 → 清空微任务 → (必要时)渲染 → 下一个宏任务。
  5. setTimeout(0) 为什么不是 0ms? ------ 最小延迟钳制 + 主线程排队 + 后台节流。
  6. await 之后是啥? ------ 微任务(就是 .then 的语法糖)。

5.2 自测清单(每条能写出例子才算过)

  • 能说出调用栈 / Web API / 任务队列 / 事件循环四个角色各自干什么
  • 能默写宏任务清单和微任务清单
  • 能用一句话说清"为什么微任务先于下一个宏任务"
  • 会用"推演表"(栈/微/宏/输出四列)推演任意异步代码
  • 能解释 setTimeout(0) 不是 0ms 的三个原因
  • 能解释微任务风暴为什么饿死宏任务和渲染

第 6 段(20min)练习①:推演经典输出顺序(code\event-loop-basic.js)

目标:先在纸上推演 ,再运行验证。卡住再看提示。 📄 参考实现已放好:code\event-loop-basic.js(含推演表注释 + 4 个变式 + 运行结果)。

6.1 练习内容与分步提示

主任务:推演下面代码的输出顺序,并在文件注释里画出推演过程:

js 复制代码
console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => console.log('3'));
console.log('4');
  • 第 1 步:先标出每一行的"同步 / 宏任务 / 微任务"。
  • 第 2 步:按"同步 → 清空微任务 → 宏任务"三段写出顺序。
  • 第 3 步:画一张推演表(栈/微/宏/输出四列,参考 4.3)。
  • 第 4 步 :用 node event-loop-basic.js 运行验证。

变式(练手,写出预测再运行)

js 复制代码
// 变式 A:微任务里又排微任务
setTimeout(() => console.log('A'), 0);
Promise.resolve().then(() => { console.log('B'); Promise.resolve().then(() => console.log('C')); });
Promise.resolve().then(() => console.log('D'));
// 预测:B D C A  (清空到空:B → D → C,然后才到宏任务 A)

// 变式 B:setTimeout 里的 Promise
setTimeout(() => { Promise.resolve().then(() => console.log('inner')); }, 0);
setTimeout(() => console.log('outer'), 0);
// 预测:inner 先于 outer(第一个宏任务产生的微任务,在第二个宏任务前清空)

// 变式 C:同步里穿插
for (const i of [1, 2, 3]) { setTimeout(() => console.log(i), 0); }
Promise.resolve().then(() => console.log('微'));
console.log('同步');
// 预测:同步 → 微 → 1 2 3

6.2 验证清单(每一条都应通过)

  • 主任务输出 1 4 3 2,注释里有完整推演表
  • 三个变式预测正确,且每条都能说出归属(宏/微/同步)

6.3 常见报错/错误认知排查

错误认知 正确理解
"Promise 的回调是同步的" .then 是微任务,同步代码之后才执行
"微任务和宏任务按注册顺序混排" 宏任务之间夹着整轮微任务清空
"setTimeout(0) 立即执行" 排队 + 钳制,只是"尽快"
"async 函数一调用就全异步" 到第一个 await 前是同步执行的

第 7 段(20min)练习②③:改造加深 + jsv9000 可视化(code\event-loop-advanced.js)

目标:把 async/awaitqueueMicrotaskrequestAnimationFrame 混进代码,预测顺序并运行验证。 📄 参考实现已放好:code\event-loop-advanced.js(Node 可跑的核心部分 + 浏览器专属的 rAF 部分)。

7.1 练习②:进阶混合推演

分步提示(先自己写预测,再运行)

js 复制代码
// ① async/await + queueMicrotask 混排(Node 与浏览器都能跑)
console.log('1');
setTimeout(() => console.log('2'), 0);
async function a() {
  console.log('3');
  await Promise.resolve();
  console.log('4');
}
a();
queueMicrotask(() => console.log('5'));
console.log('6');
// 推演:同步 1,3,6 → 微任务:await 后的 4、queueMicrotask 的 5(按序)→ 宏任务 2
// 预测:1 3 6 4 5 2
  • 第 1 步:标出每一行的归属(同步/宏/微)。
  • 第 2 步 :注意 a()3 是同步输出的(await 之前)。
  • 第 3 步:微任务队列里 4、5 的入队顺序。
  • 第 4 步node event-loop-advanced.js 验证前半部分。

7.2 练习②(浏览器部分):rAF 加入战局

requestAnimationFrame 只在浏览器存在,在浏览器控制台运行

js 复制代码
setTimeout(() => console.log('timer'), 0);
requestAnimationFrame(() => console.log('rAF'));
Promise.resolve().then(() => console.log('micro'));

推演micro 是微任务,必然最先(清空阶段);rAF渲染阶段 执行(微任务清空后、下一个宏任务之前);timer 是下一个宏任务,通常最后。但 rAF 与 timer 的先后受浏览器帧调度影响,以实际运行为准------这正是第 3 步要用 jsv9000 / 实际运行验证的原因。

7.3 练习③:jsv9000.app 可视化验证

  • 打开 www.jsv9000.app/ ,把主任务和变式代码粘进去。
  • Run ,逐步观察三块动画:Call Stack(栈)、Macro Task Queue(宏任务队列)、Micro Task Queue(微任务队列) 的进出。
  • 验证你的推演表,并把关键观察写进 code\README.md 的笔记区(比如:"看到 .then 进的是 Micro 队列,setTimeout 进的是 Macro 队列")。

观察清单(完成标准)

  • 能指出"每次宏任务执行后,Micro 队列被清空,然后 Macro 队列才取下一个"
  • 能指出 await 之后那行代码出现在 Micro 队列里
  • 能复述一次 tick 中栈/队列的变化过程

第 8 段(10min)思考提高题:先独立想,再看提示

思考题①:为什么 Promise 的 .then 被设计成微任务?如果它是宏任务会怎样?

提示

  • 从"时序一致性"想:.then 回调需要紧跟在当前同步代码之后、且不被打断地执行。
  • 如果是宏任务,setTimeout 和它公平排队------用户点击事件、I/O 就可能插在 .then 之前,导致"数据刚更新又被覆盖 / 链式状态被外部打断"。
  • 具体反例:fetch('/a').then(() => setState(A)); setState(B) 如果 then 是宏任务,B 可能渲染在 A 之前。微任务保证"本轮的连续逻辑一口气跑完"。

思考题②:循环里连续 await 多个任务,是串行还是并行?

提示

  • 先想清楚 await 的本质:它让出控制权,后续代码进微任务。在 for 循环里写 await task()每次都要等上一个完成 ------这是串行 (等价 task().then(() => task())...)。
  • 如果想让它们"同时推进",要用 Promise.all([...]) 先全部发起,再一次 await 结果。
  • 试跑:for (let i = 0; i < 3; i++) { await sleep(i); } 耗时约等于三个延迟之和;而 Promise.all 约等于最大延迟。这是并发优化的核心。

思考题③:渲染在"微任务清空后、下一宏任务前",对"改 DOM + 读布局"意味着什么?

提示

  • 浏览器通常把多次 DOM 修改合并到一次渲染 。但如果代码在"改完 DOM"后立刻同步读布局 (如 el.offsetHeightgetBoundingClientRect()),浏览器被逼着强制同步重排(layout thrashing),合并优化就失效了。
  • 反例:循环里交替"写样式 + 读 offsetHeight"→ 每次读写都触发一次重排,性能崩塌。
  • 正确姿势:先批量改,再一次读 ;或把读操作放到 requestAnimationFrame 里,让它和渲染同一帧。

第 9 段(10min)测验与收尾

9.1 测验

  • 打开 test\测验.md,限时 15 分钟完成 10 题(巩固 5 + 提升 3 + 横向 2)。
  • 做完再翻答案,把错题按"概念错误 / 推演失误 / 表达不清"分类标记,回到讲义对应章节重读。

9.2 今日能力自检(打分 1--5,低于 3 分明天补)

能力 自评
能说出四个角色(栈/Web API/队列/事件循环)如何协同
能默写宏任务与微任务清单 + 核心铁律
能独立画出任意异步代码的推演表并推对
能解释 setTimeout(0)await、微任务风暴三个细节
能用 jsv9000 验证并解释 tick 中的栈/队列变化

📌 今日核心速记卡(可截图/摘抄带走)

javascript 复制代码
单线程 = 同一时刻只做一件事;异步 = 等待外包给 Web API,主线程不阻塞

调用栈 Call Stack:正在执行的函数(后进先出);栈空,才取任务

宏任务:setTimeout / setInterval / I/O / UI事件 / rAF
微任务:Promise.then / await 之后 / queueMicrotask / MutationObserver

铁律:每执行完一个宏任务 → 清空【整个】微任务队列 → 才轮到下一个宏任务
一次 tick:执行宏任务 → 清空微任务 →(必要时)渲染 → 取下一个宏任务

setTimeout(0) ≠ 0ms:嵌套≥5层约4ms / 主线程忙要排队 / 后台标签页节流1s
await 之后的代码 = 微任务(.then 语法糖);async 函数体在 await 前是同步的
微任务风暴会饿死宏任务与渲染;渲染在微任务清空后、下一宏任务前

❌ 常见误区清单(学完自查,哪个说过/想过就划掉)

  • "setTimeout(0) 立即执行" ------ ❌ 排队 + 最小延迟钳制,只是"尽快"
  • "Promise 的回调是同步的" ------ ❌ .then 是微任务,同步代码之后才跑
  • "宏任务和微任务按注册顺序混排执行" ------ ❌ 宏任务之间夹一整轮微任务清空
  • "async 函数一调用就全异步" ------ ❌ 到第一个 await 前是同步执行的
  • "微任务队列每轮只执行一个" ------ ❌ 是清空式,新产生的也要执行完
  • "requestAnimationFrame 和 setTimeout 排同一队" ------ ❌ rAF 在渲染阶段,天然对齐帧
  • "await 会阻塞主线程" ------ ❌ 是让出控制权,主线程继续跑其他代码
  • "页面卡死是因为异步任务太多" ------ ❌ 往往是同步阻塞或微任务风暴
相关推荐
小帅不太帅1 小时前
给大家推荐一个特别好用的专为 AI Agent 打造的最快浏览器
前端·agent·浏览器
做前端的娜娜子1 小时前
#浏览器存储方案:localStorage、sessionStorage 与 Cookie
前端·面试·掘金·金石计划
用户921080262861 小时前
1. Ant Design X Vue 项目介绍:结构、组件和启动方式
前端
摸鱼研究员1 小时前
neverthrow,ts 中优雅的异常处理方案
前端·javascript
jingchao19981 小时前
Cannot read properties of null (reading ‘insertBefore‘)
前端·javascript·vue.js
计算机魔术师1 小时前
新文章
前端
爱跳舞的烤冷面2 小时前
自学嵌入式第N天(Linux篇——文件编程)
java·开发语言·前端
IT_陈寒2 小时前
Vite的热更新突然失效,原来我忽略了这个配置
前端·人工智能·后端
怪奇云呼军2 小时前
闪电智能VoiceAgent 如何管理呼入、接听、桥接和挂断状态?
java·前端·网络·数据库·人工智能