事件循环(Event Loop)
1. 什么是事件循环?
JavaScript 是一门单线程语言,同一时间只能做一件事,但这并不意味着单线程就是阻塞——实现单线程非阻塞的方法就是事件循环。
JS 中所有任务可以分为:
- 同步任务:在主线程上排队执行的任务。
- 异步任务:从主线程发起、被丢到任务队列,待主线程空闲时再被推入执行。
执行流程:同步任务进入主线程;异步任务进入任务队列;主线程内任务执行完毕为空时,会去任务队列读取对应任务推入主线程执行。上述过程不断重复,就是事件循环。
2. 宏任务与微任务
仅分为同步/异步并不准确——异步任务还可以再细分为微任务(microtask) 和宏任务(macrotask):
console.log(1);
setTimeout(() => { console.log(2); }, 0);
new Promise((resolve) => {
console.log('new Promise');
resolve();
}).then(() => { console.log('then'); });
console.log(3);
输出:1 → new Promise → 3 → then → 2。
- 同步任务:
console.log(1)、new Promise里的console.log、console.log(3)。 - 微任务:
Promise.then。 - 宏任务:
setTimeout。
执行顺序:每执行完一个宏任务,就清空当前所有微任务,再去取下一个宏任务。
3. 常见宏任务与微任务
微任务(microtask):
Promise.then/Promise.catch/Promise.finallyMutationObserver(监听 DOM 变化)process.nextTick(Node 环境)queueMicrotask
宏任务(macrotask):
setTimeout/setInterval/setImmediate(Node)- I/O 操作、网络请求、用户事件
requestAnimationFrame(与渲染相关)
4. async / await 与事件循环
async函数返回一个 Promise。await后若是非 Promise 值,会被Promise.resolve包装;await会阻塞后面的代码(把后续代码加入微任务队列)。- 同步代码先执行 → 微任务清空 → 再回到
async函数中执行之前被阻塞的代码。
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() { console.log('async2'); }
console.log('script start');
setTimeout(() => console.log('settimeout'));
async1();
new Promise((resolve) => { console.log('promise1'); resolve(); })
.then(() => console.log('promise2'));
console.log('script end');
执行结果:
script start → async1 start → async2 → promise1 → script end → async1 end → promise2 → settimeout
5. 总结:执行顺序
- 执行同步代码。
- 同步代码执行完后,依次清空所有微任务队列。
- 取一个宏任务执行。
- 重复步骤 2-3,循环往复。
来源整理自:vue3js.cn 面试官系列



