页面卡死本质是主线程被长同步任务阻塞,解决关键是将大任务拆分为小块、让出控制权,并把CPU密集型操作移至Web Worker。

页面卡死,本质是主线程被长时间同步任务占满,事件循环无法及时处理用户交互、渲染或新任务。避免卡死,关键不是“绕开”事件循环,而是“顺应”它——把大任务切碎、让出执行权、把重活交给后台。
拆分长任务,主动交还控制权
遇到需要遍历数万条数据、执行复杂计算或批量 DOM 操作时,不要用一个 for 循环干到底。那样会堵死调用栈,用户点击没响应,动画停摆,连滚动都卡住。
- 用 setTimeout(fn, 0) 或 queueMicrotask(fn) 把大任务切成小块,每处理一小部分就暂停,等事件循环下一轮再继续
- 微任务(如
Promise.then)优先级更高,适合需要快速响应的逻辑;宏任务(如setTimeout)更适合控制节奏,比如模拟帧率(16ms 一帧) - 示例:处理 10 万个元素,每次只处理 100 个,然后 setTimeout 调度下一批
把 CPU 密集型操作移出主线程
像图像压缩、加密解密、大数据排序、文件哈希计算这类纯计算任务,根本不该在主线程做。它们不依赖 DOM,也不需要浏览器 API,却最容易拖垮页面。
- 使用 Web Worker 创建独立线程运行这些逻辑,主线程只负责收发消息、更新 UI
- Worker 中不能访问 window、document 等全局对象,但可自由使用 JSON、ArrayBuffer、SharedArrayBuffer(需注意同步)
- 现代框架和构建工具(如 Webpack、Vite)已支持 Worker 模块化写法,接入成本大幅降低
警惕隐式同步阻塞
有些代码看起来“没在循环”,实则悄悄霸占主线程。比如:
- 未节流的 resize / scroll / input 事件监听器,高频触发导致任务堆积
- 一次性渲染超长列表(如 5000 行表格),强制重排重绘耗时严重
- 滥用 JSON.stringify 序列化巨大对象,或 RegExp 执行回溯爆炸的正则
- 解决方案:对高频事件加防抖/节流;长列表用虚拟滚动;用 performance.now() 或 DevTools 的 Performance 面板定位长任务
善用微任务调度时机
微任务队列在每次宏任务结束后立即清空,且不触发渲染,适合做“紧接当前操作之后、但又不打断流程”的事。
- 比如在一次 DOM 更新后,立刻收集变更并批量上报,而不是等下次 setTimeout
- queueMicrotask 比 Promise.then 更轻量,无状态、无异常捕获开销,是现代推荐方式
- 注意:别在微任务里递归调用自己,否则会饿死事件循环,导致页面完全无响应











