深入理解JavaScript事件循环机制:宏任务与微任务的执行顺序

作者:代码拾荒

时间:2026-10-25

浏览:353

本文深入解析JavaScript单线程模型下的事件循环机制,详细区分宏任务(如setTimeout)与微任务(如Promise、process.nextTick)的执行优先级。通过具体代码案例,阐明同步代码、微任务队列与宏任务队列在每一轮循环中的执行顺序,帮助开发者准确掌握异步编程的执行逻辑。

文章目录

JavaScript采用单线程模型且为非阻塞模式。若采用多线程,当不同线程同时对同一DOM节点进行添加或删除操作时,浏览器难以确定以哪个线程的结果为准。为降低复杂性,JavaScript自诞生起便设计为单线程运行。

JavaScript的事件循环机制包含主线程(负责同步代码)和任务队列(处理异步代码)。任务队列进一步细分为宏任务队列和微任务队列。

原理概述

1. 宏任务(macro-task)包括:

script(整体代码块)、setTimeout、setInterval、setImmediate。

在Node.js环境中,宏任务的执行优先级为:setImmediate > setTimeout / setInterval。

2. 微任务(micro-task)包括:

Promise、MutationObserver(用于观察DOM树结构变化并做出相应处理)、process.nextTick(Node.js环境特有)。

微任务的执行优先级为:process.nextTick > Promise > MutationObserver。

原理概述

执行顺序遵循:同步代码 > 微任务 > 宏任务。

1. 宏任务和微任务均属于任务队列范畴。当一段代码开始执行时,首先执行宏任务中的同步代码部分。

2. 在第一次事件循环中,整个JavaScript脚本被视为一个宏任务进行运行。

3. 若执行过程中遇到setTimeout等宏任务,则将该宏任务内部的回调函数推入宏任务队列,待下一轮宏任务执行时调用。

4. 若执行过程中遇到Promise.then()等微任务,则将其推入微任务队列。在当前宏任务的同步代码执行完毕后,立即依次执行所有微任务。

5. 当本轮循环中所有的同步脚本及微任务队列中的事件执行完毕后,该轮循环结束,随即开始第二轮循环,如此往复。

原理概述

案例解析

<script>
setTimeout(() => {
    console.log('4')
}, 100)
new Promise((resolve) => {
    console.log('1')
    resolve("成功")
}).then(data => {
    console.log(data)
})
console.log('2')
</script>

具体分析如下:

1. 该段代码作为宏任务进入主线程执行。

2. 首先遇到setTimeout等宏任务,将其内部函数推入宏任务队列,等待下一轮宏任务执行时调用。

3. 接着遇到Promise,其同步代码部分立即执行,而回调函数被分发至微任务队列。

4. 执行console.log('2')并立即输出,随后检查是否存在微任务。由于存在then的回调函数,立即执行该微任务。

5. 第一轮宏任务执行完毕。

6. 开始第二轮宏任务,执行宏任务队列中的setTimeout函数。

对上述代码进行升级测试:

setTimeout(() => {
    console.log('1') 
}, 1000)
new Promise((resolve) => {
    console.log('2') 
    setTimeout((params) => {
        resolve("4")
        console.log("3"); 
    }, 100)
}).then(data => {
    console.log(data) 
})
console.log('5') 

能否先将promise.then分发到微任务中?

答案是否定的。因为只有当Promise状态变为resolved(成功回调)后,才能执行.then。在当前场景下,resolve位于setTimeout宏任务中,必须等待setTimeout执行完毕后,才能触发.then中的微任务。

具体解析如下:

1. 该段代码作为宏任务进入主线程。

2. 首先遇到等待时间为1000ms的setTimeout,记为s1,并将其内部函数推入宏任务队列,等待下一轮执行。

3. 接着遇到Promise,其同步代码立即执行。内部包含一个等待时间为100ms的setTimeout,记为s2,且Promise的resolve操作也在此处,因此s2也被放入下一轮宏任务队列。

4. 执行console.log('5')并立即输出。

5. 第一轮宏任务执行结束。检查当前是否有可执行的微任务,若无则执行宏任务。

6. 开启第二轮宏任务,发现队列中有两个宏任务。根据等待时间长短比较,时间短的先执行。先执行s2,发现其中包含微任务promise.then,立即执行该微任务,随后再执行s1。

最终的输出顺序为:2 5 3 4 1。

以上为个人经验分享,希望能为大家提供参考,也希望大家多多支持本站。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章