事件循环中addEventListener回调与微/宏任务的优先级及执行阶段
浏览器事件循环优先级问题
代码示例
console.log("11111"); setTimeout(() => { console.log("22222"); }, 5000); Promise.resolve().then(() => { console.log("33333"); }); document.getElementById("test").addEventListener("click", () => { console.log("44444 +"); }); // 10秒同步延迟,留出时间触发点击事件 const syncWait = (ms) => { const end = Date.now() + ms; while (Date.now() < end) continue; }; syncWait(10000); console.log("55555");
实际输出
11111 55555 33333 44444 + 22222
问题
在浏览器事件循环中,通过addEventListener触发的回调函数,与微任务队列、宏任务队列回调的精确优先级是怎样的?换句话说,它们分别在事件循环的哪个具体阶段从各自队列中出队执行?
解答
浏览器事件循环的核心执行顺序和各任务的阶段划分如下:
- 优先执行所有同步代码:调用栈中的同步任务会按顺序全部执行完毕,比如示例里的
console.log("11111")、10秒同步延迟、console.log("55555")都属于这一步。 - 一次性清空微任务队列:同步代码执行完、调用栈清空后,会立刻执行微任务队列里的所有任务,
Promise.resolve().then()这类回调就属于微任务,所以示例中的33333会紧跟同步代码输出。 - 分阶段处理宏任务队列:微任务全部处理完成后,才会进入事件循环的各个阶段处理宏任务,不同类型的宏任务对应不同的执行阶段:
addEventListener触发的用户交互回调(比如click)属于事件触发阶段的宏任务,这个阶段在定时器阶段之前,所以已触发的交互回调会比定时器回调先执行。setTimeout这类定时器回调属于定时器阶段的宏任务,会在事件触发阶段之后处理。
对应示例场景:同步代码执行的10秒内,点击test元素会把click回调加入事件触发宏任务队列;setTimeout到5秒时,其回调会被加入定时器宏任务队列。同步代码跑完后,先执行微任务的33333,接着事件循环先处理事件触发阶段的click回调输出44444 +,最后处理定时器阶段的22222。
内容的提问来源于stack exchange,提问作者Sha'an
相关产品推荐
相关产品推荐

