JS 执行机制与异步队列
系列文章目录
《JavaScript 基础与进阶笔记》(前期偏基础巩固与常见面试点,后续进入闭包、异步、工程化等进阶主题)
- 第 01 篇:数据类型与类型判断
- 第 02 篇:变量声明与作用域
- 第 03 篇:闭包与高阶函数
- 第 04 篇:函数工厂
- 第 05 篇:this 指向与绑定
- 第 06 篇:原型与原型链
- 第 07 篇:类与继承
- 第 08 篇:JS 执行机制与异步队列(本文)
文章目录
前言
用户可见的 JavaScript 主线程在任一时刻通常只执行一段同步代码;定时器、Promise、DOM 等「稍后执行」的逻辑依赖宿主与引擎协作的调度模型。理解调用栈、宏任务与微任务怎样交替消费,是阅读输出顺序题、安排异步流程(含 async/await)以及解释框架中 nextTick 等行为的基础。本篇先建立执行栈画面,再归结事件循环中的一轮处理,最后给出可对照运行的示例与易错点。
一、单线程、调用栈与「先跑完当前同步代码」
- 单线程(主线):日常所说的「JS 是单线程」多指负责 DOM 与用户交互的主线程上的 JS 执行模型;
Web Worker等可在独立线程运行脚本,但与主线程通过消息通信,不能直接操作 DOM,此处不展开。 - 调用栈:函数调用形成栈帧的压入与弹出;当前栈上的代码连续执行,直到栈空或让出(例如
await之后的续体由引擎另行调度)。 - 同步块不可分割:一段同步代码执行期间,不会被「插入」另一个宏任务回调去打断;计时器到期、
Promise决议等只会在合适的时机把回调放入队列,待当前同步工作告一段落后再处理。
由此得到直觉:先顺序执行完当前同步代码(以及随后必须立刻清掉的微任务),再轮到计时器等宏任务。
二、执行上下文(与作用域的衔接)
每次进入函数、模块或全局脚本,都会创建对应的执行上下文(概念层可理解为:词法环境、变量环境、对外层引用、以及 this 绑定等信息的载体)。执行上下文的进栈与出栈伴随调用栈变化,与第 2 篇中的词法作用域、作用域链描述的是同一套运行时的不同切面:前者偏「谁在上层、谁在栈里」,后者偏「标识符解析沿链查找」。本篇侧重何时执行、谁先谁后;提升、块级作用域等仍以第 2 篇为准。
三、事件循环:一轮里大致发生什么
「事件循环」是引擎与宿主之间的协作约定,不同宿主细节略有差异,教学上可采用下列简化模型:
- 从宏任务队列取出一个任务执行(整个脚本的首次执行也可视为一个宏任务单元)。
- 该宏任务对应的同步代码跑完后,依次清空微任务队列(若执行微任务时又产生新微任务,会继续排到当前轮末尾,直到微任务队列为空)。
- 必要时由浏览器插入渲染等步骤(与刷新率、
requestAnimationFrame相关,后文略提)。 - 再取下一个宏任务,重复上述过程。
要点:微任务优先级高于「尚未开始的下一个宏任务」;同一轮里可以连续执行很多个微任务。
四、宏任务与微任务:常见成员
宏任务多由宿主调度入队,例如:
setTimeout、setInterval(到期后回调作为宏任务)- I/O、部分网络回调(环境与实现相关)
- UI 渲染相关调度(表述上归在浏览器事件循环模型中,不必死记与某个 API 一一绑定)
微任务多由引擎维护,例如:
Promise.prototype.then/catch/finally的回调queueMicrotask显式入队的任务MutationObserver的回调在浏览器中常以微任务形式调度
注意:「哪些是宏 / 微」不必背诵穷尽列表,核心是排队规则与一轮内的先后顺序。
五、推导「输出顺序」的固定步骤
遇到面试输出题,可按下面顺序心算:
- 标出所有同步
console.log的执行顺序。 - 标出本轮结束后将入队的微任务(含
Promise.then、queueMicrotask等)。 - 标出将入队的宏任务(如
setTimeout回调)。 - 同步结束后:先清空全部微任务(含微任务里新长的微任务),再执行下一个宏任务。
下面是与常用讲义一致的经典例子(输出在注释中):
console.log("1"); // 同步
setTimeout(() => console.log("2"), 0); // 宏任务
Promise.resolve().then(() => console.log("3")); // 微任务
queueMicrotask(() => console.log("4")); // 微任务
console.log("5"); // 同步
// 输出顺序:1, 5, 3, 4, 2
含义简述:1、5 为同步;本轮宏任务(脚本)尾巴上顺次执行微任务 3、4;2 排在之后的宏任务 turn。
六、Promise 构造器与 async/await
Promise构造器形参(executor)在创建Promise时同步调用;其中resolve()/reject()会决议 Promise,但.then注册的回调仍为微任务,在同步段之后执行。async函数:调用后返回 Promise;函数体在第一个await之前的代码同步执行。await右侧:若为 Thenable,其后的代码相当于放进微任务链(与「拆成.then」的教学模型一致);因此会出现「先打印同步的D,再进入await之后的逻辑」的现象。
const delay = (ms) => new Promise((r) => setTimeout(r, ms));
async function run() {
console.log("A"); // 同步(进入 run 后立即执行)
await delay(100);
// 从续体开始可视为微任务(delay 完成后触发)
console.log("B");
await Promise.resolve();
console.log("C"); // 微任务
}
run();
console.log("D"); // 同步
// 输出顺序:A, D, B, C(B、C 之间无其它输出时)
setTimeout(fn, 0):并不代表「立即插队到当前同步代码中间」;最短延迟受环境限制(浏览器里常见为约 4ms 下限等实现细节,Node 里计时器行为另有规范,面试题通常不抠具体毫秒,只需知道晚于本轮同步与微任务)。
七、requestAnimationFrame 与渲染
requestAnimationFrame 的回调安排在下一次重绘前,语义上服务于动画与读布局,不宜与「宏任务 / 微任务」做简单等同(部分教材写「既不是宏也不是微」即提醒勿硬套)。实践中要记住:它不适合替代精确定时器;与 setTimeout 混排时顺序以环境实现为准,题目中不如 Promise + setTimeout 常见。
八、工程上为何关心微任务
- 输出顺序与 Bug 复现:同一轮里多次
then与setTimeout交错,顺序错了就难以排查。 - 批量更新:如 Vue 2 的
$nextTick、Vue 3 的内部调度,常依靠微任务把多次数据变更合并到一次刷新前后处理,避免不必要的中间渲染。 - 异步控制流:
Promise链与async/await本质都建立在决议与微任务之上,后续专门讨论 Promise 时可以再展开状态机细节。
九、易混淆点归纳
- 只有
.then/.catch/.finally回调才是微任务;new Promise(() => { ... })里写的代码多半是同步的。 async函数从头执行到第一个await(不含续体)仍是同步。- 微任务可以嵌套产生微任务:会一直 drain 到空,才可能进入下一个宏任务;死循环入队微任务会卡死主线程。
instanceof、闭包、this与本篇正交:执行顺序题要在对调用栈与队列有把握的前提下再叠加上去。- 跨环境表述:浏览器与 Node 在宏任务细分(如
process.nextTick在 Node 中具有更前位置)上不完全一致,遇到 Node 题需单独查当前版本约定。
十、思考与练习
1. 下面代码输出什么?
Promise.resolve().then(() => console.log("p1"));
console.log("sync");
Promise.resolve().then(() => console.log("p2"));
解析:sync 先同步输出;随后在同一轮清空微任务:p1、p2。结果为 sync, p1, p2。
2. 为何「微任务太晚堆积」可能导致页面卡顿?
解析:微任务在渲染等步骤之前会被连续清空;若单轮内微任务过多,会推迟浏览器响应其它工作与视觉更新,主观上表现为卡顿。
3. setTimeout(0) 与 Promise.resolve().then(...) 谁先执行?
解析:当前同步代码结束后,先执行微任务里的 then,再轮到计时器宏任务(在典型浏览器模型下)。
总结
- 主线程上同步代码先执行;调用栈与执行上下文描述「如何跑完这一段」。
- 事件循环一轮内:跑一个宏任务 → 清空微任务队列 → 再取下一宏任务(简化模型)。
Promise.then、queueMicrotask等为微任务;setTimeout等回调多为宏任务;这是输出题的支点。async/await的续体可按微任务理解;await之前仍有同步段。
下一篇计划整理 数组常用算法与手写练习(与大纲中「常用算法」部分衔接;防抖、节流等也可在后续专题中成文)。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐
所有评论(0)