JavaScript 任务执行机制(Event Loop)
JavaScript 是单线程的,意味着同一时间只能执行一个任务。为了避免长时间运行的任务阻塞主线程,JavaScript 采用了异步任务队列和**事件循环(Event Loop)**来调度任务。
📌 1. 任务类型
JavaScript 任务分为两大类:
-
同步任务(Synchronous)
- 立即执行,不需要等待
- 例如:普通函数、变量赋值、for 循环等
-
异步任务(Asynchronous)
- 需要等待,稍后执行
- 例如:
setTimeout、setInterval、Promise、fetch、async/await
📌 2. 执行流程
JavaScript 执行任务的流程如下:
-
同步任务 直接进入 主线程(调用栈,Call Stack)。
-
异步任务 进入 任务队列,等待执行。
-
任务队列 又分为:
- 宏任务队列(Macro Task Queue)
- 微任务队列(Micro Task Queue)
-
主线程执行完当前任务后,会先清空微任务队列,然后再取出一个宏任务执行。
-
重复以上步骤,形成 Event Loop(事件循环) 。
📌 3. 宏任务 vs 微任务
JavaScript 将异步任务分为 宏任务(Macro Task) 和 微任务(Micro Task) ,微任务优先级更高。
| 任务类型 | 分类 | 例子 |
|---|---|---|
| 宏任务(Macro Task) | setTimeout、setInterval | 定时器回调 |
setImmediate(Node.js) | 立即执行的回调 | |
I/O | 文件、网络请求 | |
UI 渲染 | 浏览器的渲染任务 | |
| 微任务(Micro Task) | Promise.then/catch/finally | Promise 回调 |
MutationObserver | 监听 DOM 变化 | |
process.nextTick(Node.js) | Node.js 专有 |
💡 规则:
- 微任务优先级高于宏任务,会先执行微任务队列,再执行宏任务队列中的任务。
📌 4. 事件循环(Event Loop)
事件循环负责管理任务的执行顺序,基本流程如下:
1. 执行同步任务(主线程执行)
2. 清空微任务队列(所有 Promise 回调)
3. 执行一个宏任务(如 setTimeout 回调)
4. 清空微任务队列
5. 执行下一个宏任务...
6. 重复以上步骤
📌 5. 代码执行顺序示例
🌟 示例 1
console.log('start');
setTimeout(() => {
console.log('setTimeout');
}, 0);
Promise.resolve().then(() => {
console.log('promise');
});
console.log('end');
🔹 执行过程
- 同步任务:
console.log('start')→ 执行 - 同步任务:
console.log('end')→ 执行 - 微任务:
Promise.then→ 加入 微任务队列 - 宏任务:
setTimeout→ 加入 宏任务队列 - 执行微任务:
console.log('promise') - 执行宏任务:
console.log('setTimeout')
🔹 最终输出
start
end
promise
setTimeout
🌟 示例 2
console.log('A');
setTimeout(() => {
console.log('B');
}, 0);
Promise.resolve().then(() => {
console.log('C');
}).then(() => {
console.log('D');
});
console.log('E');
🔹 执行顺序
- 同步任务:输出
A - 同步任务:输出
E - 微任务:
Promise.then(C → D)加入 微任务队列 - 宏任务:
setTimeout加入 宏任务队列 - 执行微任务:输出
C - 执行微任务:输出
D - 执行宏任务:输出
B
🔹 最终输出
A
E
C
D
B
📌 6. 常见问题
❓ 为什么 setTimeout(fn, 0) 不是立即执行?
setTimeout(fn, 0) 并不会立即执行,而是:
setTimeout(fn, 0)进入 宏任务队列。- 等当前同步任务和所有微任务执行完毕后,才会执行 宏任务。
示例:
setTimeout(() => console.log('Timeout'), 0);
Promise.resolve().then(() => console.log('Promise'));
console.log('Sync');
输出:
Sync
Promise
Timeout
因为 Promise 是微任务,先执行。
❓ async/await 的本质是什么?
async/await 只是 Promise 的语法糖,await 相当于 Promise.then(),是一个微任务。
示例:
async function test() {
console.log(1);
await Promise.resolve();
console.log(2);
}
test();
console.log(3);
输出:
1
3
2
console.log(1)直接执行await之后的代码 变成微任务console.log(3)先执行console.log(2)作为微任务最后执行
📌 7. 重点总结
-
JavaScript 是单线程,通过 Event Loop 管理异步任务执行顺序。
-
任务分为 同步任务(立即执行)和 异步任务(放入任务队列)。
-
异步任务分为:
- 宏任务(Macro Task) :
setTimeout、setInterval、I/O 任务 - 微任务(Micro Task) :
Promise.then、async/await
- 宏任务(Macro Task) :
-
微任务优先级更高,在每个宏任务执行后,先清空微任务队列,再执行下一个宏任务。
-
setTimeout(fn, 0)不会立即执行,因为它是宏任务,必须等所有微任务执行完再执行。
✅ 理解 Event Loop 的执行机制,是写好 JavaScript 异步代码的关键! 🚀