死循环卡死怎么排查
点某个按钮后页面直接卡死无响应,CPU 飙满,不报错也不动,只能强制刷新,不知道是哪段循环停不下来。
有人这样说「点个按钮页面直接卡死,CPU飙到100%,啥提示都没有,只能强制刷新。」
能替你选的都选好了。不想动,直接点下面「交给 AI 办」,点一下就开始做;想改,就点一下要的那个。
已替你选好想改就点一下
只有你知道的不填就先空着,成品里留【待填】
还想怎么改可以不选
高级选项一般不用动 · 这件事通常 1 分钟左右做好
示例成品全文按上面这组示例选项真跑出来的
先纠正一下:这段不是“无限死循环”,是固定跑 10 亿次,但对浏览器主线程来说和死机没区别。
for (let i = 0; i < 1000000000; i++) { /* 没 break */ }浏览器主线程是单线程,它一直跑这个同步循环,就没空处理点击、渲染、React/Vue 更新,所以页面直接无响应,CPU 飙到 100%。
一、怎么排查
1. 最有效:卡死时直接暂停 JS
这招对同步死循环基本一击定位。
步骤:
- 点击按钮前,先打开 Chrome DevTools。
- 点按钮,让页面卡死。
- 切到 DevTools → Sources。
- 点脚本暂停按钮,快捷键通常是
F8。 - 浏览器会停在当前正在执行的 JS 那一行。
你会直接看到停在:
for (let i = 0; i < 1000000000; i++) { /* 没 break */ }调用栈会显示:
onClick -> for循环2. 用 Performance 录长任务
步骤:
- DevTools → Performance。
- 点录制按钮。
- 点页面上那个按钮。
- 等几秒后停止录制。
- 找红色长任务,点进去看 Bottom-Up / Call Tree。
耗时最高的函数会指向那个 for 循环。
3. 土办法:二分注释
把可疑代码注释掉,或者把 10 亿改成 1000 试试:
for (let i = 0; i < 1000; i++) { /* ... */ }如果不卡了,说明就是循环次数太大。
二、怎么修
不要在主线程同步跑这种大循环。
方案 A:时间分片,让页面能喘气
async function runInChunks(total, chunkSize, onProgress) {
for (let i = 0; i < total; i += chunkSize) {
const end = Math.min(i + chunkSize, total);
for (let j = i; j < end; j++) {
// 你的实际逻辑写这里,用 j 代替原来的 i
}
if (onProgress) onProgress(end, total);
// 让出主线程,让浏览器处理点击、渲染
await new Promise(resolve => setTimeout(resolve, 0));
}
}用法:
await runInChunks(1000000000, 1000000, (done, total) => {
console.log(`${done}/${total}`);
});这样每跑 100 万次就让出一次主线程,页面不会卡死。
注意:async/await 本身不能让出主线程,必须用 setTimeout 这类宏任务。
方案 B:纯计算丢 Web Worker
如果循环里是纯计算,直接丢给 Worker,主线程完全不阻塞。
// worker.js
self.onmessage = function (e) {
let sum = 0;
for (let i = 0; i < e.data; i++) {
sum += i;
}
self.postMessage(sum);
};// main.js
const worker = new Worker('worker.js');
worker.postMessage(1000000000);
worker.onmessage = e => {
console.log('结果:', e.data);
};页面该怎么操作还怎么操作,不会卡。
三、React / Vue 配合
点击后设置 loading,禁用按钮,防止用户重复点。
React
const [loading, setLoading] = useState(false);
async function handleClick() {
setLoading(true);
await runInChunks(1000000000, 1000000);
setLoading(false);
}Vue
const loading = ref(false);
async function onClick() {
loading.value = true;
await runInChunks(1000000000, 1000000);
loading.value = false;
}如果这段循环只是测试代码,直接删掉或者改小数字。10 亿次空转本身没有实际意义。