主线程发起异步操作 → 宿主环境处理 → 异步完成后产生任务 → 进入相应的任务调度队列 → 事件循环选择合适的任务执行 → 回到主线程执行回调。
text
JavaScript 主线程
│
│ 发起异步操作
↓
宿主环境处理
│
│ 异步操作完成
↓
产生后续任务
│
↓
不同任务来源 / 调度队列
│
↓
事件循环选择可运行任务
│
↓
JavaScript 主线程
│
↓
执行任务中的 JavaScript
这条主线才是第一性原理。
一、追问:任务是不是只有一个队列?
不是。 现代浏览器规范不是简单的:
text
一个宏任务队列
+
一个微任务队列
而是:
text
不同 Task Source
↓
一个或多个 Task Queue
↓
Event Loop
↓
选择可运行的 Task
例如浏览器会区分不同来源的任务:
text
用户交互任务
定时器任务
网络相关任务
DOM 相关任务
其他任务
↓
浏览器进行调度
不同任务之间并不是简单按照"谁先进队列谁永远先执行"。
HTML Standard 明确允许用户代理根据情况选择不同任务队列,例如可以提高鼠标、键盘交互相关任务的处理优先级,以保证页面响应性。(HTML Living Standard)
二、那 Promise 到底是什么?
Promise 是一种异步抽象;Promise 的
.then()、.catch()、.finally()回调会通过微任务机制执行。
javascript
Promise.resolve().then(() => {
console.log('A');
});
核心过程:
text
Promise 状态完成
↓
then 回调安排为微任务
↓
当前 JavaScript 执行结束
↓
执行微任务
↓
A
Promise 的回调使用微任务队列,这是当前 Web 平台模型的一部分。(MDN Web Docs)
三、那微任务是什么?
核心结论
微任务用于把一段 JavaScript 安排到
当前任务结束后、下一次任务开始前执行。
例如:
javascript
console.log(1);
Promise.resolve().then(() => {
console.log(2);
});
console.log(3);
结果:
text
1
3
2
因为:
text
当前任务
↓
1
↓
注册微任务
↓
3
↓
当前任务结束
↓
执行微任务
↓
2
而且:
微任务执行过程中产生的新微任务,也会继续执行,直到微任务队列清空。 (MDN Web Docs)
四、setTimeout(0) 应该怎么理解?
setTimeout到期后,会将对应的任务加入定时器相关的任务调度流程;它不是"0 毫秒后立即执行"。
javascript
setTimeout(() => {
console.log('timer');
}, 0);
应该理解成:
text
注册定时器
↓
满足定时条件
↓
产生 timer task
↓
进入相应任务队列
↓
等待事件循环调度
↓
最终执行回调
所以:
0表示最小延迟,不表示立即执行。
HTML Standard 对 setTimeout() 产生任务的描述也是基于 task,而不是"立即执行回调"。(HTML Living Standard)
五、Promise 和 setTimeout(0) 谁先?
这个问题仍然可以回答:
在典型浏览器场景下,Promise 回调会先于
setTimeout(0)回调执行。
javascript
setTimeout(() => {
console.log('A');
}, 0);
Promise.resolve().then(() => {
console.log('B');
});
通常:
text
B
A
但高级面试应该说:
Promise 回调进入微任务队列;当前任务结束后会执行微任务检查,而定时器产生的 task 需要等待后续事件循环调度。
六、事件循环到底干什么?
这是整套模型的核心。
事件循环不断运行调度流程,从当前可运行的任务中选择合适的任务执行,并在任务结束后处理微任务等后续步骤。
简化理解:
text
事件循环
↓
选择一个可运行任务
↓
执行任务
↓
处理微任务
↓
浏览器根据需要进行渲染等工作
↓
继续下一轮调度
MDN 对现代浏览器运行时的概括也是:事件循环每轮执行一个任务,然后处理微任务,再根据需要进行渲染。(MDN Web Docs)
七、所以整套知识应该这样记
text
JavaScript 异步
│
↓
主线程发起异步操作
│
↓
宿主环境处理
│
↓
异步操作完成
│
↓
产生后续任务
│
↓
┌────────────┴────────────┐
↓ ↓
不同 Task Source 微任务机制
↓ ↓
不同任务调度队列 Promise 等回调
│ │
└──────────┬──────────────┘
↓
事件循环
↓
选择可运行任务
↓
执行 JavaScript
↓
处理微任务
↓
下一轮事件循环
最终面试版
JavaScript 异步的核心不是"宏任务和微任务",而是任务调度。
JavaScript 主线程发起异步操作后,交给宿主环境处理;异步操作完成后产生后续任务,由事件循环根据任务来源、可运行状态等进行调度,最终回到 JavaScript 主线程执行。
Promise 的回调属于微任务机制,会在当前任务结束后的微任务检查阶段执行;
setTimeout(0)则是在定时条件满足后产生相应任务,等待事件循环后续调度,所以并不是立即执行。现代浏览器并不是简单的"一个宏任务队列 + 一个微任务队列",而是存在不同的任务来源和任务队列,浏览器可以根据调度策略选择合适的任务。 (HTML Living Standard)
你真正应该记住的主线
主线程 → 宿主处理 → 产生任务 → 任务调度 → 事件循环 → 主线程执行 → 微任务检查
这比传统的:
宏任务 → 微任务 → 事件循环
更接近现代浏览器实际的调度模型。