事件循环 · 任务、microtask,与那一圈的所有时机
JavaScript 是单线程的,却要同时响应点击、运行定时器、等待网络——靠的是一个持续循环的 事件循环 (event loop)。它的规则可以概括为一句:取一个 macrotask 执行完 → 把 microtask 全部清空 → 到时机就渲染 → 再取下一个。 理解这一圈,就能解释「为什么 Promise.then 比 setTimeout(0) 先执行」「页面为什么卡顿」「Vue/React 为何不卡」。
本系列从这一圈本身出发,逐个拆解向队列提交任务的入口: 骨架 → macrotask 家族 → microtask → rAF/rIC → postTask → 落地。每页都在真实浏览器中执行, 单步观察队列的增删,并直观对比卡顿与流畅的差别。
一圈是怎么转的:同步 → microtask → 渲染 → 下一个 macrotask
把整段 script 视为第一个 macrotask,单步推进一轮事件循环:macrotask 队列、microtask 队列与渲染机会各自何时被取用。
macrotask 大家族:不止 setTimeout,而且它们并不等价
macrotask 不止 setTimeout,也彼此不等价。实测五种任务的排队先后,以及 setTimeout 连环嵌套超过五层后的 4ms 钳制台阶。
microtask:在「下一个 macrotask」之前全部执行完
microtask 的三个特性——优先于 macrotask、把一轮同步改动批处理成一次回调、清空到底期间不渲染,全部在真实浏览器中执行验证。
两个特殊任务:渲染前的 rAF,空闲时的 rIC
rAF 在绘制前执行、rIC 在绘制后的空闲期执行。实测同帧 rAF 回调共用一个 timestamp,以及 rAF 排在同帧还是下一帧取决于谁排的它。
Scheduler.postTask:给 macrotask 装上「优先级」
scheduler.postTask 给 macrotask 补上三档优先级与 TaskController 取消,与 setTimeout、requestIdleCallback 逐项对比,并介绍 scheduler.yield。
落地:框架与性能优化,本质都是在与事件循环打交道
长任务切片、Vue nextTick 的 microtask 批量更新、React Scheduler 的 MessageChannel 时间切片、scroll 用 rAF 节流,以及它们与 INP 的关系。
一句话串起来
rAF / rIC 位于渲染前后两个特殊时机 (见 rAF / rIC);postTask 为 macrotask 补上了优先级 (见 Scheduler.postTask)。 工程上所有关于卡顿与流畅的取舍 (见 落地),本质都是「把代码放在这一圈的哪个时机、是否按时让出主线程」。相关链接
- HTML Standard · Event loops html.spec.whatwg.org 事件循环的规范定义:task queues / microtask checkpoint / rendering 的「processing model」原文。
- MDN · JavaScript execution model developer.mozilla.org 从执行模型角度概述 job queue / event loop、run-to-completion 与 task / microtask 的优先级划分。
- Jake Archibald · In The Loop youtube.com (JSConf) 本系列参考来源之一:用动画清晰讲解 tasks / microtasks / rAF 的顺序,以及 microtask 阻塞渲染 (starvation)。
- Paul Irish · requestAnimationFrame scheduling for nerds medium.com rAF 的排队细节:event handler 里排 = 同帧、rAF 里排 = 下一帧;同帧回调共享同一个 frame timestamp;rAF 不自动限流 / coalesce 靠自己。对应 rAF / rIC 页的 rAF 排队一节。
-
MDN · Using microtasks in JavaScript
developer.mozilla.org
queueMicrotask指南:何时用 microtask、与 Promise / setTimeout 的取舍。 -
MDN · Scheduler.postTask()
developer.mozilla.org
三档优先级、TaskController、与
scheduler.yield();Scheduling APIs 的入口。 -
Can I use · Scheduler.postTask
caniuse.com
scheduler.postTask的实时浏览器支持矩阵 (Safari 尚未支持,需等价模拟)。 - MDN · Window.requestAnimationFrame() developer.mozilla.org 在下一次绘制前执行回调;timestamp 参数、后台标签暂停、one-shot 需逐帧续排。
- web.dev · Optimize long tasks web.dev 长任务为何伤 INP、yield to main 的几种写法 (postTask / scheduler.yield / 退路),以及 isInputPending。
-
MDN · requestIdleCallback()
developer.mozilla.org
空闲回调的
deadline.timeRemaining()/didTimeout/timeout选项与兼容性。