推荐答案
JavaScript 的事件循环中,宏任务(Macrotask)和微任务(Microtask)的执行顺序如下:
- 执行同步代码: 首先执行 JavaScript 代码中的同步部分,这些代码会立即执行。
- 执行宏任务: 当同步代码执行完毕后,会从宏任务队列中取出一个任务执行。常见的宏任务包括:
setTimeout
setInterval
setImmediate
(Node.js 环境)requestAnimationFrame
- I/O 操作 (例如网络请求、文件读写)
- 用户交互事件 (例如点击、滚动)
- 执行微任务: 当一个宏任务执行完毕后,会立即检查微任务队列,并执行队列中的所有微任务。常见的微任务包括:
Promise.then
和Promise.catch
async/await
(实际上是基于 Promise 实现的)queueMicrotask
(新的API)MutationObserver
- 循环执行: 执行完所有微任务后,事件循环会再次从宏任务队列中取出一个任务执行,如此循环往复,直到宏任务队列为空。
总结: 执行顺序是:同步代码 -> 宏任务 -> 清空微任务队列 -> 宏任务 -> 清空微任务队列 ... 循环进行。
本题详细解读
宏任务与微任务的概念
- 宏任务(Macrotask): 代表的是一些比较耗时的操作,例如 I/O,定时器等。它们会被放入宏任务队列中,每次事件循环只会执行一个宏任务。
- 微任务(Microtask): 代表的是一些需要尽快执行的操作,例如 Promise 的回调,MutationObserver 的回调等。它们会被放入微任务队列中,每次宏任务执行完毕后,会清空微任务队列。
执行顺序详解
- 同步代码优先: JavaScript 引擎会先执行同步代码,这些代码直接在调用栈中执行。例如,变量声明、函数调用等。
- 宏任务队列与微任务队列: 当执行遇到异步操作(例如
setTimeout
,Promise.then
),会将其回调函数放入对应的队列中。宏任务会放入宏任务队列,微任务会放入微任务队列。 - 事件循环的运作:
- 事件循环会不断地检查调用栈是否为空。
- 如果调用栈为空,则会检查宏任务队列是否为空。
- 如果宏任务队列不为空,则会取出一个宏任务执行。
- 执行宏任务后,会立即检查微任务队列是否为空。
- 如果微任务队列不为空,则会依次执行所有微任务。
- 清空微任务队列后,事件循环会再次回到第二步,继续检查宏任务队列。
- 循环往复,直到宏任务队列为空。
- 关键点:
- 一个宏任务执行完毕后,会立即清空微任务队列,才会执行下一个宏任务。 这保证了微任务的执行顺序优先于下一个宏任务。
- 微任务的执行具有“插队”性质。 在当前宏任务的执行过程中,即使产生了新的宏任务,也会等到当前宏任务清空完微任务队列后,才会执行新的宏任务。
示例代码
-- -------------------- ---- ------- ------------------- -------- --------------------- - -------------------------- -- --- --------------------------------- - ------------------------ ------------------ - ------------------------ --- ------------------- ------
执行顺序分析:
script start
(同步代码)script end
(同步代码)promise1
(微任务)promise2
(微任务)setTimeout
(宏任务)
说明:
- 同步代码
script start
和script end
先执行。 Promise.then
产生的微任务promise1
和promise2
在当前宏任务(同步代码)执行完后立即执行。setTimeout
产生的宏任务setTimeout
最后执行。
通过理解宏任务和微任务的执行顺序,可以更好地理解 JavaScript 的异步机制,并写出更高效、更可靠的代码。