页面假死是主线程被微任务队列持续占用导致无法响应输入和渲染的状态;当微任务不断产生新微任务时,事件循环卡在清空微任务队列阶段,阻塞渲染、交互及宏任务执行,并引发内存滞留与GC压力。

页面假死不是崩溃,而是主线程被持续占用、无法响应用户输入和渲染更新的状态。当微任务执行期间不断产生新微任务,事件循环就卡在“清空微任务队列”这一步,永远无法推进到渲染和下一轮宏任务。
微任务队列必须一次性清空
浏览器规定:每个宏任务结束后,必须立即、全部、不间断地执行完当前微任务队列中的所有任务,中间不插入任何渲染、输入响应或定时器检查。如果每个正在执行的微任务又调用 queueMicrotask、Promise.then 或触发 MutationObserver 回调,新任务会追加到队尾,队列长度只增不减。
- 没有“暂停”机制——微任务之间不会让出控制权
- 即使单个微任务只耗时 0.1ms,累积 1000 个也会阻塞主线程超 100ms,远超一帧 16ms 的容忍阈值
- 用户点击、滚动、键盘输入全部堆积在事件队列里,但主线程没机会去读取它们
渲染和交互被彻底跳过
由于事件循环卡在微任务层,后续流程全部停滞:
- UI 渲染(reflow + repaint)被无限推迟,页面看起来“冻住”
-
requestAnimationFrame回调不会触发,动画停止 - 已到期的
setTimeout和setInterval被挂起,直到微任务队列终于清空 - Chrome 可能在约 1000 层嵌套后抛出
RangeError: Maximum microtask execution depth exceeded,但多数情况下只是无声卡死
内存压力同步加剧
每个排队的微任务都持有其创建时的闭包环境。若这些环境里引用了 DOM 节点、大数组或组件实例,这些对象就无法被垃圾回收:
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
立即学习“Java免费学习笔记(深入)”;
- 微任务队列长期不空 → 引用长期存活 → 内存滞留时间拉长
- 浏览器 GC 只能在宏任务间隙安全运行,而宏任务一直等不到执行机会
- 引擎被迫更激进调度 GC,反而造成更明显、更频繁的主线程停顿
典型危险模式举例
以下写法看似无害,实则极易引发假死:
Promise.resolve().then(() => { /* 处理逻辑 */; Promise.resolve().then(...); })queueMicrotask(() => { render(); queueMicrotask(render); })-
MutationObserver回调中修改了被观察的 DOM,再次触发同一 observer
这类逻辑不会报栈溢出错误,也不会中断执行,但会让主线程陷入“永动”状态,直到用户强制刷新或关闭标签页。

















