前提
JavaScript是单线程的,即一次只能执行一个任务。
因此为了处理异步操作,比如定时器、网络请求,就有了事件循环机制,它是是其处理异步任务的核心,来确保单线程环境下的高效运行,防止阻塞线程。
事件循环的主要组成部分:调用栈(Call Stack)、任务队列(Task Queue)、微任务队列(Microtask Queue),还有浏览器提供的Web APIs。
-
调用栈(Call Stack) :执行同步代码,遵循后进先出。
-
Web APIs:浏览器提供的异步功能(如
setTimeout、fetch)。
而那宏任务和微任务就是任务队列中的不同类型任务。
宏任务
-
由宿主环境(浏览器、Node.js)发起的任务。
-
常见类型:
setTimeout/setInterval- I/O 操作(如文件读写)
- DOM 事件回调(如点击事件)
requestAnimationFrame(浏览器)setImmediate(Node.js)
微任务
-
由 JavaScript 自身发起的任务
-
常见类型:
Promise.then()/Promise.catch()MutationObserver(监听 DOM 变化)queueMicrotask()process.nextTick()(Node.js,优先级最高)
流程
当运行一段代码时,先运行同步代码,再清空队列里的当前所有微任务,再进行一个宏任务,再清空队列里的微任务。
同步任务 → 微任务 → 宏任务
以下代码为例
console.log('Start'); // 1
setTimeout(() => {
console.log('Timeout 1'); // 4
Promise.resolve().then(() => console.log('Promise in Timeout 1')); // 5
}, 0);
setTimeout(() => {
console.log('Timeout 2'); // 6
Promise.resolve().then(() => console.log('Promise in Timeout 2')); // 7
}, 0);
Promise.resolve().then(() => console.log('Promise 1')); // 3
console.log('End'); // 2
即
- 同步代码执行(
Start、End)。 - 同步代码执行后处理微任务(
Promise 1)。 - 执行第一个宏任务(
Timeout 1),其微任务立即执行。 - 执行第二个宏任务(
Timeout 2),同样处理其微任务。
关键点:
- 微任务优先:每次宏任务结束后,必须清空微任务队列。