在 javascript 事件循环中,微任务(如 promise.then、mutationobserver)会在当前宏任务(如 dom 事件监听器回调)执行完毕后、ui 渲染前立即执行。要实现这一行为,只需在事件监听器内部创建并返回一个 promise,或者使用 queuemicrotask 将代码放入微任务队列。浏览器引擎会在处理完当前调用栈中的所有同步代码后,清空微任务队列,然后再继续下一个宏任务或渲染步骤。

如何在点击事件中触发微任务
要在点击事件监听器后执行微任务,最直接的方法是在回调函数中使用 Promise.resolve().then() 或 queueMicrotask()。当用户点击按钮时,事件监听器作为宏任务被推入任务队列并被执行。在监听器内部,通过上述 API 提交的回调会被加入微任务队列。由于微任务优先级高于后续的宏任务和渲染,它们会先于页面重绘执行。

const button = document.getElementById('myButton');
button.addEventListener('click', () => {
console.log('1. 同步代码:监听器开始执行');
// 方式一:使用 Promise
Promise.resolve().then(() => {
console.log('2. 微任务:Promise 回调执行');
});
// 方式二:使用 queueMicrotask
queueMicrotask(() => {
console.log('3. 微任务:queueMicrotask 回调执行');
});
console.log('4. 同步代码:监听器结束执行');
});
运行上述代码并点击按钮,控制台输出顺序为:1、4、2、3。这证明了微任务在当前宏任务(点击事件)的同步代码全部执行完后才运行。
微任务与 UI 渲染的时序关系
微任务的执行时机位于当前宏任务结束后和下一次 UI 渲染之前。这意味着在事件监听器中触发的微任务可以修改 DOM 状态,而不会导致强制同步布局(forced synchronous layout),因为此时尚未进行样式计算和几何计算。这种机制允许开发者在响应事件后异步更新视图,同时保持界面的流畅性。

// 模拟在事件监听器中修改 DOM 并观察渲染时机
const div = document.getElementById('target');
div.addEventListener('click', () => {
console.log('A: 点击事件开始');
// 修改 DOM
div.textContent = 'Updated';
// 提交微任务
queueMicrotask(() => {
console.log('B: 微任务执行,DOM 已修改但未渲染');
// 此时如果读取 offsetHeight,可能会触发重排,但通常发生在渲染前
});
console.log('C: 点击事件结束');
});
// 注意:实际的渲染发生在所有微任务清空之后
在上述示例中,'A' 和 'C' 是同步输出的,'B' 是微任务输出的。浏览器会在 'C' 之后、'B' 之前完成当前的绘制周期准备,并在 'B' 执行完毕后,如果仍有其他待处理任务,再进入下一轮渲染。这种时序确保了微任务中的 DOM 操作能高效地合并到下一次渲染中。
基于 Promise 链式调用的具体执行问题
在事件监听器中,如果连续使用多个 .then() 链式调用,每个 .then() 都会创建一个新的微任务。这些微任务会按顺序依次执行,直到队列清空。例如,如果在监听器中写 Promise.resolve().then(() => {}).then(() => {}),第一个 .then 的回调会在当前宏任务结束后立即执行,而第二个 .then 的回调会在第一个 .then 的同步代码执行完后,作为新的微任务被调度。这种链式结构保证了微任务内部的严格顺序执行,且始终优先于任何后续的宏任务。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











