Yasin

Yasin

JS 事件循环、单线程、宏任务、微任务

JS 事件循环、单线程、宏任务、微任务


1. 为什么是单线程

JS 最初为浏览器设计,需要操作 DOM。
如果多线程同时操作同一个 DOM 节点会产生冲突,所以 JS 引擎只有一个主线程

但单线程意味着:同一时间只能做一件事 → 遇到耗时操作会阻塞页面。
所以需要异步机制来解决这个问题,这就是事件循环存在的原因。


2. 浏览器是多线程的

虽然 JS 引擎是单线程,但浏览器本身有多个线程协作:

  • JS 引擎线程:执行 JS 代码(只有一个)
  • GUI 渲染线程:渲染页面(与 JS 线程互斥)
  • 网络请求线程:处理 fetch/XHR
  • 定时器线程:处理 setTimeout/setInterval
  • 事件触发线程:处理点击、键盘等事件

耗时任务交给其他线程处理,完成后把回调放入任务队列,JS 主线程空闲时再取出执行。


3. 什么是事件循环

事件循环就是 JS 主线程不断重复的工作流程:

执行一个宏任务
清空所有微任务
(可能)页面渲染
取下一个宏任务
    ...

4. 宏任务(Macrotask)

每次事件循环取一个宏任务执行。

常见宏任务:

  • 整体脚本(<script>,第一个宏任务)
  • setTimeout / setInterval
  • UI 事件回调(clickinput
  • I/O 回调

5. 微任务(Microtask)

优先级高于宏任务,每次宏任务结束后立刻清空所有微任务。

常见微任务:

  • Promise.then/catch/finally
  • queueMicrotask()
  • MutationObserver

关键:微任务不是"下一次循环",而是"当前宏任务结束后、下一个宏任务开始前"立刻执行。


6. 三者联系图

JS 主线程(单线程)
┌─────────────────────────────────┐
│         事件循环(Event Loop)    │
│                                 │
│  1. 取一个宏任务执行              │
│     ├── 整体 script(第一个)     │
│     ├── setTimeout 回调          │
│     └── UI 事件回调              │
│                                 │
│  2. 当前宏任务结束               │
│     └── 清空微任务队列           │
│         ├── Promise.then        │
│         ├── queueMicrotask      │
│         └── MutationObserver    │
│                                 │
│  3. 页面渲染(如有需要)          │
│                                 │
│  4. 取下一个宏任务 → 重复        │
└─────────────────────────────────┘

7. 通过例子验证

console.log("1"); // 同步

setTimeout(() => {
  console.log("4"); // 宏任务
}, 0);

Promise.resolve().then(() => {
  console.log("3"); // 微任务
});

console.log("2"); // 同步

执行过程:

  1. 整体脚本(第一个宏任务)开始
  2. console.log('1') → 输出 1
  3. setTimeout 回调放入宏任务队列
  4. Promise.then 回调放入微任务队列
  5. console.log('2') → 输出 2
  6. 当前宏任务结束,清空微任务 → 输出 3
  7. 进入下一个宏任务(setTimeout)→ 输出 4

结果:1 2 3 4


8. 微任务产生微任务的情况

微任务执行时如果又产生微任务,会继续追加到微任务队列尾部,在本轮全部清完:

Promise.resolve()
  .then(() => {
    console.log("微任务 1");
    return Promise.resolve();
  })
  .then(() => {
    console.log("微任务 2");
  });

setTimeout(() => {
  console.log("宏任务");
}, 0);

输出:微任务 1微任务 2宏任务

宏任务必须等所有微任务都清完才能执行。


9. 一句话总结

概念 说明
单线程 JS 同一时间只做一件事
事件循环 不断取任务执行的机制
宏任务 每次循环取一个,粒度大
微任务 每个宏任务后立刻清空,优先级高

**单线程是原因,事件循环是解决方案,宏任务和微任务是执行队列的两个优先级层次。**输出:微任务 1微任务 2宏任务

宏任务必须等所有微任务都清完才能执行。


9. 一句话总结

概念 说明
单线程 JS 同一时间只做一件事
事件循环 不断取任务执行的机制
宏任务 每次循环取一个,粒度大
微任务 每个宏任务后立刻清空,优先级高

单线程是原因,事件循环是解决方案,宏任务和微任务是执行队列的两个优先级层次。