前端的事件循环主要有三种情况:
- 1.js的事件循环
- 2.浏览器的事件循环
- 3.nodejs的事件循环
JavaScript 事件循环(Event Loop)的执行流程
JavaScript 是 单线程 的,它通过 事件循环(Event Loop) 来管理同步任务、异步任务(包括宏任务和微任务)。
1. 任务分类
在事件循环中,任务分为 同步任务(Synchronous) 和 异步任务(Asynchronous) 。
同步任务
- 立即执行,直接进入 主线程(Call Stack)
- 例如:普通函数调用、
console.log()
、变量声明等
异步任务
- 由 任务队列(Task Queue) 处理,等待主线程空闲时执行
- 分为 宏任务(Macro-task) 和 微任务(Micro-task)
2. 宏任务(Macro-task)
宏任务主要包括:
setTimeout
setInterval
setImmediate
(Node.js)requestAnimationFrame
I/O 任务
UI 渲染
特点:
- 宏任务会进入宏任务队列,等待主线程执行完所有同步任务后再执行
- 每次事件循环(Event Loop)都会 优先执行一个宏任务,然后再执行所有微任务
3. 微任务(Micro-task)
微任务主要包括:
Promise.then/catch/finally
MutationObserver
queueMicrotask
process.nextTick
(Node.js)
特点:
- 微任务会进入微任务队列
- 每次执行一个宏任务后,立即执行所有微任务
4. 事件循环(Event Loop)执行顺序
- 执行主线程的同步任务(Call Stack 里的代码)
- 执行所有微任务(Micro-task)
- 执行一个宏任务(Macro-task)
- 重复以上步骤
5. 示例代码
console.log('1'); // 同步任务
setTimeout(() => {
console.log('2'); // 宏任务
}, 0);
Promise.resolve().then(() => {
console.log('3'); // 微任务
});
console.log('4'); // 同步任务
执行流程
console.log('1')
→ 同步任务,直接执行setTimeout()
→ 宏任务,加入宏任务队列Promise.then()
→ 微任务,加入微任务队列console.log('4')
→ 同步任务,直接执行- 执行所有微任务(
console.log('3')
) - 执行一个宏任务(
console.log('2')
)
最终输出
1
4
3
2
6. 加入多个宏任务 & 微任务的例子
console.log('A');
setTimeout(() => {
console.log('B');
}, 0);
Promise.resolve().then(() => {
console.log('C');
}).then(() => {
console.log('D');
});
console.log('E');
执行流程
-
console.log('A')
(同步,直接执行) -
setTimeout()
(宏任务,放入宏任务队列) -
Promise.then()
(微任务,放入微任务队列) -
console.log('E')
(同步,直接执行) -
执行所有微任务
console.log('C')
console.log('D')
-
执行一个宏任务
console.log('B')
最终输出
A
E
C
D
B
7.Promise中存在async和await的情况
var a
var b = new Promise((resolve) => {
console.log(1);
setTimeout(() => {
console.log(2);
resolve(2)
}, 1000)
}).then(() => {
console.log(3);
}).then(() => {
console.log(4);
}).then(() => {
console.log(5);
})
a = new Promise(async (resolve) => {
console.log(a)
await b
console.log(a)
await (a)
resolve(true)
console.log(6);
})
console.log('end');
执行流程
阶段 1:同步代码执行
-
声明变量
a
var a
变量提升,初始值为undefined
。
-
执行
var b = new Promise(...)
-
同步执行 Promise 构造函数:
- 输出
1
- 设置
setTimeout(() => resolve(2), 1000)
(这是一个 宏任务,1秒后加入队列)。
- 输出
-
-
执行
a = new Promise(...)
-
同步执行构造函数中的
async
函数:console.log(a)
:此时a
尚未被赋值,输出undefined
await b
:暂停执行,将后续代码包装为 微任务(记为微任务1) ,等待b
完成。
-
-
执行
console.log("end")
- 输出
end
- 输出
阶段 2:处理微任务队列(此时为空)
- 当前没有可执行的微任务(
b
尚未完成,微任务1
仍在等待)。
阶段 3:执行宏任务(1秒后)
-
setTimeout
回调触发- 执行
resolve(2)
,将b
的状态变为 fulfilled。 - 触发
b.then(() => console.log(3))
,将它的回调加入 微任务队列(记为微任务2) 。
- 执行
阶段 4:处理微任务队列
-
执行微任务2
- 输出
3
- 触发下一个
.then(() => console.log(4))
,加入微任务队列(记为微任务3)。
- 输出
-
执行微任务3
- 输出
4
- 触发下一个
.then(() => console.log(5))
,加入微任务队列(记为微任务4)。
- 输出
-
执行微任务4
- 输出
5
- 输出
-
执行微任务1(之前暂停的
a
的构造函数)-
恢复
await b
后的代码:console.log(a)
:此时a
已被赋值为 Promise(状态为 pending),输出Promise { <pending> }
await a
:等待a
自身变为 fulfilled,将后续代码包装为 微任务(记为微任务5) ,但此时a
仍为 pending,因此微任务5 被阻塞,无法加入队列。
-
阶段 5:后续事件循环
- 微任务5 永远不会被加入队列,因为
a
的状态始终为 pending(resolve(true)
被await a
阻塞)。 - 事件循环发现没有其他任务(微任务和宏任务队列均为空),结束运行。
最终输出
1
[AsyncFunction: a]
end
2
3
4
5
Promise { <pending> }
8. 总结
- 同步任务 直接执行
- 同步任务执行后会执行所有的微任务
- 微任务队列中没有更多的微任务会去执行一个宏任务
- 宏任务执行后,再检查是否有更多的微任务
- 如果有微任务,执行所有微任务
- 事件循环会不断重复这个过程
这样,JavaScript 实现了异步执行的机制,同时保持单线程模型的运行方式