代码执行顺序
js
async function async1() {
console.log("a");
await async2();
console.log("b");
}
async function async2() {
console.log("c");
}
console.log("d");
async1();
setTimeout(() => {
console.log("e");
}, 0);
new Promise((resolve, reject) => {
console.log("f");
resolve();
}).then(() => {
console.log("g");
});
以下是这段代码的输出顺序以及详细的执行过程解释:
输出顺序:
d
a
c
f
b
g
e
详细解析:
1. 同步代码优先执行
- JavaScript 是单线程的,代码执行会先按照同步任务顺序执行,遇到异步任务时,会将它们放到任务队列(宏任务或微任务)中等待处理。
- 代码执行从上到下,逐行解释。
2. 具体执行流程
(1) console.log('d');
- 这是同步代码,直接执行。
- 输出:
d
(2) async1()
调用
- 进入
async1
函数:console.log('a');
是同步代码,直接执行。- 输出:
a
- 执行
await async2();
:- 调用
async2()
:console.log('c');
是同步代码,直接执行。- 输出:
c
async2()
返回的是一个Promise
(async
函数总是返回一个Promise
),但没有await
或.then
的后续逻辑,因此返回的Promise
会被忽略。
- 调用
await
会暂停async1
后续代码(即console.log('b');
)的执行,等待async2()
返回的Promise
被解决。async1
的剩余代码会被放入 微任务队列。
(3) setTimeout(...)
- 将回调函数(
console.log('e');
)放入 宏任务队列,等待事件循环调度。
(4) new Promise(...)
- 立即执行
new Promise
的同步部分:console.log('f');
是同步代码,直接执行。- 输出:
f
- 调用
resolve()
,会将.then()
的回调放入 微任务队列。
3. 事件循环处理任务队列
- 同步代码执行完后,主线程会检查 微任务队列 并执行所有的微任务。
- 然后再检查 宏任务队列,执行一个宏任务(如
setTimeout
)。
(1) 微任务队列:
- 按照注册顺序执行微任务:
async1
剩余代码(console.log('b');
):- 输出:
b
- 输出:
Promise.then(...)
的回调(console.log('g');
):- 输出:
g
- 输出:
(2) 宏任务队列:
- 执行
setTimeout
的回调(console.log('e');
):- 输出:
e
- 输出:
总结
最终输出的顺序为:
d
a
c
f
b
g
e
关键点:
- 同步任务优先执行。
await
会暂停当前async
函数后续代码,放入微任务队列。- 微任务优先于宏任务(
Promise.then
和async
的后续代码是微任务,而setTimeout
是宏任务)。
微任务和宏任务简介
JavaScript 的 事件循环机制(Event Loop)将任务分为 微任务 和 宏任务,它们的主要区别在于 调度优先级 和 执行时机。
1. 宏任务 (Macro Task)
- 定义:宏任务是主线程上执行的任务,每次事件循环会从任务队列中取出一个宏任务并执行。
- 常见的宏任务:
setTimeout
和setInterval
setImmediate
(仅 Node.js 环境支持)- I/O 操作(例如文件读写、网络请求的回调)
- UI 渲染任务(浏览器相关)
- 主代码块(
script
代码块本身)
2. 微任务 (Micro Task)
- 定义:微任务是一个更高优先级的异步任务。当前宏任务执行完成后,会立即执行所有已存在的微任务,然后再进入下一个宏任务。
- 常见的微任务:
Promise.then
,Promise.catch
,Promise.finally
MutationObserver
(监听 DOM 变化)queueMicrotask
(显式创建微任务)
3. 任务调度顺序
- 执行主代码块(属于一个宏任务)。
- 清空微任务队列(所有的微任务按顺序执行)。
- 执行下一个宏任务(如
setTimeout
回调)。 - 重复步骤 2 和 3。
示例代码分析
javascript
console.log("start");
setTimeout(() => {
console.log("macro task 1");
}, 0);
Promise.resolve()
.then(() => {
console.log("micro task 1");
})
.then(() => {
console.log("micro task 2");
});
console.log("end");
输出顺序:
start
end
micro task 1
micro task 2
macro task 1
解释:
同步代码:
console.log('start');
输出start
。console.log('end');
输出end
。
异步代码:
setTimeout
回调(宏任务)被添加到宏任务队列。Promise.resolve()
的.then
(微任务)被添加到微任务队列。
执行微任务:
- 先清空微任务队列:
micro task 1
输出。micro task 2
输出。
- 先清空微任务队列:
执行宏任务:
macro task 1
输出。
4. 还有其他任务类型吗?
除了微任务和宏任务外,还有一些其他类型的任务或调度机制:
(1) Idle Tasks
- 定义:空闲任务用于在浏览器空闲时执行一些不重要的工作。
- 示例:
requestIdleCallback
,它的回调会在浏览器空闲时调用。
(2) 渲染任务
- 定义:浏览器会在每一帧中执行渲染操作(如更新 DOM、绘制图形)。
- 说明:
- 渲染任务和 JavaScript 的任务调度是分离的。
- 一般渲染任务在微任务清空之后进行。
(3) 队列任务的分类总结
- 宏任务队列:用于调度主任务(主代码块、计时器回调等)。
- 微任务队列:用于调度高优先级的异步操作(
Promise
、MutationObserver
等)。 - 渲染队列:在微任务完成后,浏览器检查是否需要重新渲染页面。
5. 关系图解
plaintext
┌──────────────────────────────┐
│ 宏任务队列 │
│ [setTimeout, I/O, script] │
└───────────▲──────────────────┘
│
┌───────────┴──────────┐
│ 微任务队列 │
│ [Promise, MutationObserver] │
└──────────────────────┘
│
渲染任务(如果需要)
│
进入下一次事件循环
6. 小结
特性 | 宏任务 | 微任务 |
---|---|---|
执行时机 | 下一轮事件循环 | 当前宏任务结束后立即 |
优先级 | 较低 | 较高 |
常见任务来源 | setTimeout , setInterval , I/O | Promise , queueMicrotask |
任务队列 | 独立宏任务队列 | 独立微任务队列 |
了解这些任务类型和调度机制是深入掌握 JavaScript 异步行为的关键!