一、前言
在 JavaScript 的异步编程中,事件循环(Event Loop) 决定了代码的执行顺序。Promise 和 setTimeout 作为常见的异步操作,很多人容易混淆它们的执行顺序。
今天,我们通过一段代码深入解析 JavaScript 事件循环的执行机制,并彻底搞懂 同步任务、微任务、宏任务 的调度规则!
二、代码示例:Promise + setTimeout,谁先执行?
ts
setTimeout(() => {
console.log(1)
}, 0)
const p = new Promise((resolve, reject) => {
console.log(2)
resolve(3)
Promise.resolve(4).then(console.log)
console.log(5)
}).then(console.log)
console.log(6)
在不运行代码的情况下,你能猜出它的输出顺序吗? (建议先思考一下,再往下看解析 🤔)
三、代码执行流程详解
3.1、执行同步代码
JavaScript 会先执行所有 同步任务 ,然后再处理 异步任务(微任务 > 宏任务)
setTimeout
(宏任务) 只是被注册,不会立即执行,而是放入 宏任务队列Promise
构造函数内的代码 是同步执行的 :console.log(2)
同步执行 ,输出2
resolve(3)
只是改变 Promise 状态 ,但.then(console.log)
不会立刻执行Promise.resolve(4).then(console.log)
创建了一个微任务 ,加入 微任务队列console.log(5)
同步执行 ,输出5
console.log(6)
同步执行 ,输出6
3.2、执行微任务队列(Microtask Queue)
所有 同步代码执行完毕 后,JavaScript 进入 微任务阶段,开始执行微任务队列中的任务。
Promise.resolve(4).then(console.log)
先执行,输出4
resolve(3).then(console.log)
执行,输出3
3.3、执行宏任务队列(Macrotask Queue)
所有 微任务执行完毕 后,JavaScript 进入 宏任务阶段 ,执行 任务队列中的宏任务
setTimeout
任务被执行,输出1
四、最终执行顺序
ts
setTimeout(() => {
console.log(1)
}, 0) // 宏任务(MacroTask),延迟执行,放入任务队列
const p = new Promise((resolve, reject) => {
console.log(2) // 立即执行(同步代码)
resolve(3) // 将 `3` 作为 Promise 的解决值
Promise.resolve(4).then(console.log) // 微任务,then 立即进入微任务队列
console.log(5) // 继续执行同步代码
}).then(console.log) // `resolve(3)` 触发的 then,进入微任务队列
console.log(6) // 继续执行同步代码
- 2 // Promise 内同步执行
- 5 // Promise 内同步执行
- 6 // 主线程同步执行
- 4 // 微任务:Promise.resolve(4).then()
- 3 // 微任务:resolve(3).then()
- 1 // 宏任务:setTimeout
五、JavaScript 事件循环(Event Loop)核心规则
- 先执行所有同步代码 ,这些代码会直接进入 调用栈(Call Stack) 立即执行
- 遇到异步任务时
- 微任务(Microtask) (Promise.then、MutationObserver、queueMicrotask)进入 微任务队列
- 宏任务(Macrotask) (setTimeout、setInterval、setImmediate、I/O 操作、UI 渲染)进入 宏任务队列
- 同步代码执行完后
- 先清空微任务队列(按照先进先出的顺序执行)
- 再执行宏任务队列中的任务(setTimeout 等)
- 进入下一个事件循环,重复以上过程
六、总结
- 同步代码最先执行
- Promise.then(微任务)优先于 setTimeout(宏任务)
- 微任务的执行顺序遵循先进先出(FIFO)
- JavaScript 采用单线程机制,但通过事件循环处理异步任务
七、思考题 🤔
阅读完这篇文章后,你能正确判断下面代码的输出顺序吗?
ts
console.log('A')
setTimeout(() => {
console.log('B')
}, 0)
Promise.resolve()
.then(() => {
console.log('C')
})
.then(() => {
console.log('D')
})
console.log('E')
在评论区留下你的答案,并告诉我你的思考过程吧!🚀
希望这篇文章能帮你彻底理解 JavaScript 事件循环,欢迎点赞 👍、收藏 ⭐、留言 💬 交流!