宏任务执行完毕后立即清空微任务队列,不暂停、不插入其他操作,全部同步执行;微任务在ui渲染前运行,但耗时操作会阻塞主线程。

宏任务执行完后立即响应微任务,靠的是事件循环的强制调度机制:每次宏任务结束,引擎不跳转、不等待,直接进入微任务队列扫描阶段,并同步执行队列中所有任务,直到清空为止。
微任务不是“等一等”,而是“马上清空”
事件循环不会在宏任务结束后暂停或检查是否“该执行微任务了”,而是硬性规定:只要一个宏任务(比如 setTimeout 回调、页面初始化脚本)执行完毕、调用栈清空,就立刻开始处理微任务队列——不管队列里有几个任务,也不管它们是刚加入还是早就存在,全部按顺序跑完,中间不插入任何其他操作。
- Promise.then、queueMicrotask、MutationObserver 回调一旦入队,就锁定在本轮微任务执行窗口
- 哪怕在某个 .then 里又调用 queueMicrotask,新任务也会追加到当前队列尾部,仍属于本轮清空范围
- 这个“清空”动作是原子性的,没有中间状态,也不会被渲染或下一个宏任务打断
宏任务与微任务之间没有“间隙”
很多人误以为宏任务结束后会“歇一下”,再启动微任务。实际流程是线性的:宏任务执行 → 调用栈归零 → 立即遍历并执行微任务队列 → 队列为空 → 才考虑渲染或取下一个宏任务。这个衔接没有延迟、不依赖时间片,也不受 setTimeout(0) 这类伪“立即”宏任务干扰。
- UI 渲染发生在微任务清空之后、下一个宏任务之前(浏览器环境)
- 所以微任务能确保在 DOM 更新后、画面绘制前运行,比如用 queueMicrotask 拿到最新布局尺寸
- 而 setTimeout 回调必须等到下一轮事件循环开头,哪怕设为 0 毫秒,也得排队等前面所有微任务走完
响应快,但不是无代价
微任务的“立即响应”带来确定性,也带来风险:它全程同步执行,不释放主线程控制权。如果微任务里做大量计算、递归调用 queueMicrotask 或反复触发 Promise.then,就会阻塞渲染和后续宏任务,导致界面冻结。
- 避免在微任务中做耗时同步操作(如遍历万级数组、深克隆复杂对象)
- 需要延后到渲染后?选 requestAnimationFrame;需要真正异步?用 setTimeout 或 Web Worker
- 想兜底错误或清理资源?微任务合适;想发起网络请求?它不合适——微任务不释放线程,只是调整顺序











