

JavaScript 是单线程语言,但它却能处理复杂的并发操作(如网络请求、定时器、用户交互),这背后的秘密武器就是 事件循环(Event Loop)。本文将深入拆解宏任务与微任务的执行逻辑,通过代码示例帮你彻底搞懂执行顺序。
Promise.then、process.nextTick (Node)、MutationObserver。setTimeout、setInterval、setImmediate (Node)、I/O、UI Rendering。JavaScript 的设计初衷是作为浏览器脚本语言,主要用途是与用户互动和操作 DOM。如果它是多线程的,一个线程在删除 DOM 节点,另一个线程在编辑该节点,会带来复杂的同步问题。因此,JS 选择 单线程 执行。
为了不阻塞主线程(例如等待一个 5秒的 API 请求),JS 引入了 异步非阻塞 机制,而事件循环正是协调同步代码与异步回调执行顺序的调度员。
并不是所有的异步任务都是一样的。它们被分为两类队列:
通常是由代码本身产生的任务,优先级较高,需要在当前同步代码执行完后立即处理。
Promise.then / .catch / .finallyprocess.nextTick (Node.js 环境,优先级高于 Promise)MutationObserver (监听 DOM 变化)queueMicrotask API通常是由宿主环境(浏览器或 Node)发起的任务,每次事件循环只取一个执行。
setTimeout / setIntervalsetImmediate (Node.js)requestAnimationFrame (UI 渲染前执行,归类有些特殊,通常视为渲染阶段的一部分)<script> (整体代码本身算第一个宏任务)标准的 Event Loop 流程如下:
requestAnimationFrame 回调(如果在渲染前)。口诀:同步走完清微任务,渲染之后取宏任务。
console.log('1'); // 同步
setTimeout(() => {
console.log('2'); // 宏任务
}, 0);
Promise.resolve().then(() => {
console.log('3'); // 微任务
});
console.log('4'); // 同步解析:
'1',打印 '4'。'3'。'2'。
结果:1 -> 4 -> 3 -> 2console.log('Start');
setTimeout(() => {
console.log('Timeout'); // 宏任务
}, 0);
Promise.resolve().then(() => {
console.log('Promise 1'); // 微任务 1
// 微任务中产生新的微任务
Promise.resolve().then(() => {
console.log('Promise 2'); // 微任务 2
});
});
console.log('End');解析:
'Start', 'End'。Promise 1,执行并打印。Promise 1 执行时注册了 Promise 2,追加到当前微任务队列尾部。Promise 2,打印。Timeout 执行。
结果:Start -> End -> Promise 1 -> Promise 2 -> Timeoutasync/await 只是 Promise 的语法糖。await 这一行右边的代码是同步执行的,await 下面的代码 相当于放在了 Promise.then 中,属于微任务。
async function async1() {
console.log('async1 start');
await async2();
// 下面这行相当于 .then(() => console.log('async1 end'))
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
async1();
new Promise(function(resolve) {
console.log('promise1'); // Promise 构造函数内是同步的
resolve();
}).then(function() {
console.log('promise2');
});
console.log('script end');深度解析:
script start (同步)setTimeout 注册宏任务。async1: async1 start (同步)。async2,打印 async2 (同步)。await,将 async1 end 放入微任务队列 (微任务1)。new Promise: promise1 (同步)。resolve() 触发 then,将 promise2 放入微任务队列 (微任务2)。script end (同步)。async1 end。promise2。setTimeout。结果:
script start -> async1 start -> async2 -> promise1 -> script end -> async1 end -> promise2 -> setTimeout
(注:旧版 Chrome 曾有 Bug 导致 async1 end 比 promise2 慢,但在现代浏览器中已符合标准,遵循入队顺序)
new Promise(fn) 中的 fn 会立即执行,只有 .then 中的回调才是微任务。while(true))。process.nextTick 优先级高于 Promise。掌握事件循环的关键在于分清 同步代码、微任务 和 宏任务 的层级。始终记住:微任务是 VIP 通道,必须优先走完;宏任务是普通通道,一次只能走一个。