微任务在事件处理栈清空后执行,是因为 javascript 引擎在每次宏任务(如 dom 事件、定时器回调)执行完毕后,会立即检查并清空当前微任务队列。具体做法是:当事件处理器运行结束,引擎不会直接去取下一个宏任务,而是先遍历微任务队列,依次执行所有已排队的微任务,直到队列为空,然后才渲染页面并进入下一轮事件循环。

为什么宏任务结束后优先执行微任务
因为规范规定,每次宏任务执行完后必须清空微任务队列。这意味着只要你在事件回调中调度了微任务,它会在该回调返回后立即执行,而不是等待下一个用户交互或定时器。这种机制确保了高优先级的异步操作(如 Promise 解析、MutationObserver 通知)能尽快得到处理,而不被后续的宏任务打断。

console.log('1. Start');
setTimeout(() => {
console.log('2. Timeout callback');
}, 0);
Promise.resolve().then(() => {
console.log('3. Microtask 1');
});
new MutationObserver(() => {
console.log('4. Microtask 2');
}).observe(document.body, { childList: true });
console.log('5. End of main script');预期输出顺序为:1. Start、5. End of main script、3. Microtask 1、4. Microtask 2、2. Timeout callback。这证明了在主脚本(宏任务)结束后,微任务先于定时器回调(另一个宏任务)执行。
立即学习“Java免费学习笔记(深入)”;
微任务队列清空的具体时机是什么
微任务队列的清空发生在当前调用栈为空且当前宏任务执行完毕之后。具体来说,当浏览器完成一个宏任务(例如点击事件的处理函数)后,它会暂停获取新的宏任务,转而检查微任务队列。如果队列中有任务,就取出第一个执行;执行完后再次检查,直到队列为空。只有当微任务队列彻底清空后,浏览器才会进行可能的 UI 渲染,然后从宏任务队列中取出下一个任务执行。

// 模拟事件循环中的微任务处理逻辑
function eventLoop() {
while (true) {
// 1. 执行一个宏任务
const macrotask = getMacrotask();
if (macrotask) {
macrotask.run();
}
// 2. 宏任务结束后,立即清空微任务队列
let microtask;
while ((microtask = getMicrotask())) {
microtask.run();
}
// 3. 微任务清空后,才可能进行渲染并继续下一轮
renderIfNeeded();
}
}上述伪代码展示了核心逻辑:内层 while 循环确保在当前宏任务期间调度的所有微任务都被执行完毕。例如,如果在微任务中又调度了新的微任务,它会被加入队列并在同一轮中被执行,从而形成链式调用,直到没有更多微任务为止。


















