彻底搞懂 Event Loop!这篇文章帮你一次性吃透宏任务、微任务、执行顺序

Event Loop(事件循环) 是 JavaScript 执行机制里的核心知识点。不管是前端面试还是日常开发,只要涉及异步,就绕不开它。

这篇文章不玩高深术语,用一套真实示例,带你搞懂:

  • 同步任务宏任务微任务 到底是什么
  • 它们的执行顺序和优先级
  • 最后特别提醒:Promise.resolve().then() 到底哪个才是微任务?

🧵 为什么需要 Event Loop?

JS 是单线程,意味着同一时刻只能执行一件事。

比如:

js 复制代码
console.log('A');
console.log('B');
console.log('C');

执行结果必然是:

css 复制代码
A
B
C

这就是同步任务:顺序执行,执行完才往下走。

但是,现实开发中需要执行很多耗时任务(比如网络请求、定时器)。如果都用同步执行,页面直接卡死,体验没法要。

所以浏览器和 Node 设计了异步队列 + Event Loop,先把耗时任务挂起,空闲时再回头执行。


🗂️ 同步任务、宏任务、微任务,怎么分?

先看个对比表:

类型 代表
同步任务 普通函数、console.log、函数调用
宏任务(Macrotask) 整个 <script>setTimeoutsetIntervalsetImmediateMessageChannel(浏览器)、I/O(Node)
微任务(Microtask) Promise.then()MutationObserverqueueMicrotaskprocess.nextTick(Node 专属)

执行顺序口诀:

一个宏任务 → 执行同步任务 → 清空微任务 → 执行下一个宏任务 → 循环


✅ 浏览器执行顺序经典示例

来看个最典型的组合拳:

js 复制代码
console.log('代码开始');

setTimeout(() => {
  console.log('这是 setTimeout 回调');
}, 0);

Promise.resolve().then(() => {
  console.log('这是 promise 的回调');
});

console.log('代码结束');

🧐 输出结果:

arduino 复制代码
代码开始
代码结束
这是 promise 的回调
这是 setTimeout 回调

执行过程拆解:

  1. <script> 整个文件就是一个宏任务,先跑同步代码。
  2. setTimeout 是下一个宏任务,排到队列后面。
  3. Promise.then 是微任务,会在本轮宏任务跑完后立刻执行。
  4. 所以输出顺序是:同步 → 微任务 → 下一个宏任务

❗ 重点:Promise.resolve() 本身不是微任务!

超多人搞错,以为 Promise.resolve() 本身是微任务,其实不是!

来看个例子:

js 复制代码
console.log('程序启动');

const p1 = Promise.resolve('第一个 Promise');
const p2 = Promise.resolve('第二个 Promise');
const p3 = new Promise(resolve => {
    console.log('执行 p3 构造函数');
    resolve('第三个 Promise');
});

p1.then(value => console.log(value));
p2.then(value => console.log(value));
p3.then(value => console.log(value));

setTimeout(() => {
    console.log('第一波 setTimeout');
    const p4 = Promise.resolve('第四个 Promise');
    p4.then(value => console.log(value));
}, 0);

setTimeout(() => {
    console.log('第二波 setTimeout');
}, 0);

console.log('程序结束');

执行顺序:

javascript 复制代码
程序启动
执行 p3 构造函数
程序结束
第一个 Promise
第二个 Promise
第三个 Promise
第一波 setTimeout
第四个 Promise
第二波 setTimeout

🔑 为什么?

  • Promise.resolve() 只是返回一个已确定状态的 Promise,不会产生微任务。

  • 真正触发微任务队列的是 .then() 的回调!

  • 所以这段里:

    • 同步任务先跑完:程序启动执行 p3 构造函数程序结束
    • 再清空微任务队列:第一个 Promise第二个 Promise第三个 Promise
    • 然后执行第一个 setTimeout(宏任务):第一波 setTimeout,里面的 .then() 会生成一个新的微任务:第四个 Promise
    • 最后执行第二个 setTimeout第二波 setTimeout

🧬 MutationObserver:监听 DOM 改变的微任务

浏览器里,MutationObserver 也属于微任务。看个示例:

js 复制代码
const target = document.createElement('div');
document.body.appendChild(target);

const observer = new MutationObserver(() => {
  console.log('微任务: MutationObserver 回调执行');
});

observer.observe(target, { attributes: true, childList: true });

target.setAttribute('data-role', '123');
target.appendChild(document.createElement('span'));

这里修改了 target 的属性和子元素,MutationObserver 会把回调放进微任务队列,在本轮宏任务执行完后跑。


⚡ Node.js 专属:process.nextTick 比普通微任务还快!

Node.js 里还有个专属:process.nextTick,它优先级比普通微任务还高。

js 复制代码
console.log('启动');

process.nextTick(() => {
  console.log('执行 process.nextTick');
});

Promise.resolve().then(() => {
  console.log('执行 Promise.then');
});

setTimeout(() => {
  console.log('执行 setTimeout');
  Promise.resolve().then(() => {
    console.log('setTimeout 中的 Promise');
  });
}, 0);

console.log('收尾');

🧐 输出结果:

javascript 复制代码
启动
收尾
执行 process.nextTick
执行 Promise.then
执行 setTimeout
setTimeout 中的 Promise

✏️ queueMicrotask:手动插个微任务

需要自己把任务插进微任务队列?用 queueMicrotask

js 复制代码
console.log('准备执行');

queueMicrotask(() => {
  console.log('这是 queueMicrotask 回调');
});

console.log('执行完同步任务');

输出:

复制代码
准备执行
执行完同步任务
这是 queueMicrotask 回调

Promise.then 一样效果,都会在本轮宏任务执行完后立刻跑。


🗂️ 总结:执行顺序记住这张表

优先级 队列
🥇 最高 process.nextTick(Node 专属)
🥈 第二 Promise.thenMutationObserverqueueMicrotask
🥉 最后 宏任务:setTimeoutsetIntervalI/O

顺序一定是:

  • 当前宏任务执行完 → 清空所有微任务
  • 再执行下一个宏任务
  • 循环往复

🚀 最后的金句

Promise.resolve() 只是创建了一个已确定状态的 Promise,本身不是微任务。只有调用 .then() 后,回调才会排进微任务队列!

别再搞混了!

面试题里很多顺序题,考点就是这里。

相关推荐
wearegogog1234 小时前
基于 MATLAB 的卡尔曼滤波器实现,用于消除噪声并估算信号
前端·算法·matlab
Drawing stars4 小时前
JAVA后端 前端 大模型应用 学习路线
java·前端·学习
品克缤4 小时前
Element UI MessageBox 增加第三个按钮(DOM Hack 方案)
前端·javascript·vue.js
小二·4 小时前
Python Web 开发进阶实战:性能压测与调优 —— Locust + Prometheus + Grafana 构建高并发可观测系统
前端·python·prometheus
小沐°4 小时前
vue-设置不同环境的打包和运行
前端·javascript·vue.js
qq_419854055 小时前
CSS动效
前端·javascript·css
烛阴5 小时前
3D字体TextGeometry
前端·webgl·three.js
桜吹雪5 小时前
markstream-vue实战踩坑笔记
前端
南村群童欺我老无力.5 小时前
Flutter应用鸿蒙迁移实战:性能优化与渐进式迁移指南
javascript·flutter·ci/cd·华为·性能优化·typescript·harmonyos
C_心欲无痕5 小时前
nginx - 实现域名跳转的几种方式
运维·前端·nginx