微任务在事件处理作用域后执行,是因为 javascript 引擎将宏任务(如 dom 事件回调)和微任务(如 promise 回调、mutationobserver)分属不同的执行队列。当宏任务执行完毕并退出当前调用栈后,引擎会立即清空微任务队列,直到队列为空才进行下一轮渲染或新的宏任务调度。

Promise 回调为何比 setTimeout 先执行
Promise 的 `.then` 或 `.catch` 注册的是微任务,而 `setTimeout` 注册的是宏任务。即使 `setTimeout` 的延迟时间为 0,它也必须等待当前宏任务(即主脚本或事件回调)完全结束后,进入下一个宏任务周期才能执行。相比之下,微任务在当前宏任务结束后的“检查点”立即执行,因此顺序上永远优先于同周期的宏任务。

console.log('1. Script Start');
setTimeout(() => {
console.log('2. Timeout MacroTask');
}, 0);
new Promise((resolve) => {
console.log('3. Promise Executor');
resolve();
}).then(() => {
console.log('4. Promise MicroTask');
});
console.log('5. Script End');运行上述代码,输出顺序为:1, 3, 5, 4, 2。这证实了同步代码(1, 3, 5)最先执行,随后是微任务(4),最后才是宏任务(2)。
立即学习“Java免费学习笔记(深入)”;
MutationObserver 与 Promise 的执行优先级
MutationObserver 同样属于微任务队列。在同一个宏任务中,如果同时存在 Promise 回调和 MutationObserver 回调,它们的执行顺序取决于注册的先后顺序。先注册的微任务会先执行。这与 Promise 的行为一致,因为它们共享同一个微任务队列机制。

const observer = new MutationObserver(() => {
console.log('MutationObserver MicroTask');
});
observer.observe(document.body, { childList: true });
new Promise(resolve => {
console.log('Promise Before Resolve');
resolve();
}).then(() => {
console.log('Promise MicroTask');
});
// 触发一个变更以入队 MutationObserver
const div = document.createElement('div');
document.body.appendChild(div); // 这会触发 MutationObserver 回调入队注意:在实际浏览器环境中,DOM 变更触发的 MutationObserver 回调是在 DOM 操作完成后、当前宏任务结束前入队的。如果我们在同步代码中同时注册 Promise 和触发 DOM 变更,Promise 的 `.then` 和 MutationObserver 都会进入微任务队列。由于 Promise 的 `.then` 通常在同步代码中直接链式调用,而 MutationObserver 是由 DOM 操作隐式触发的,具体顺序需看实现细节,但通常它们都在同一批次被处理。更简单的验证方式是比较两者在纯 JS 环境下的行为,它们都属于微任务,优先级高于宏任务。
async/await 中的 await 如何影响执行流
`await` 关键字会暂停当前 async 函数的执行,并将剩余部分包装成一个微任务。这意味着 `await` 之后的代码不会立即执行,而是等到当前调用栈清空、微任务队列中的其他任务(如之前注册的 Promise 回调)执行完毕后,才会作为新的微任务重新加入队列执行。这确保了 `await` 不会阻塞整个线程,但会推迟其后续逻辑的执行时机。
async function test() {
console.log('Async Start');
await new Promise(resolve => {
console.log('Awaiting Promise');
resolve();
});
console.log('After Await');
}
test();
console.log('Sync Code After Async Call');输出顺序为:Async Start, Awaiting Promise, Sync Code After Async Call, After Await。这里 `After Await` 之所以排在最后,是因为 `await` 将 `console.log('After Await')` 包装成了微任务,它必须等待同步代码(包括 `test()` 函数本身之外的同步代码)执行完毕后,才能在微任务队列中被调度执行。


















