示例成品 · 平台演示,按左边这组点选真跑出来的
先纠正一下:这段不是“无限死循环”,是固定跑 10 亿次,但对浏览器主线程来说和死机没区别。
```js
for (let i = 0; i < 1000000000; i++) { /* 没 break */ }
```
浏览器主线程是单线程,它一直跑这个同步循环,就没空处理点击、渲染、React/Vue 更新,所以页面直接无响应,CPU 飙到 100%。
---
## 一、怎么排查
### 1. 最有效:卡死时直接暂停 JS
这招对同步死循环基本一击定位。
步骤:
1. 点击按钮前,先打开 Chrome DevTools。
2. 点按钮,让页面卡死。
3. 切到 DevTools → **Sources**。
4. 点脚本暂停按钮,快捷键通常是 `F8`。
5. 浏览器会停在当前正在执行的 JS 那一行。
你会直接看到停在:
```js
for (let i = 0; i < 1000000000; i++) { /* 没 break */ }
```
调用栈会显示:
```text
onClick -> for循环
```
### 2. 用 Performance 录长任务
步骤:
1. DevTools → **Performance**。
2. 点录制按钮。
3. 点页面上那个按钮。
4. 等几秒后停止录制。
5. 找红色长任务,点进去看 Bottom-Up / Call Tree。
耗时最高的函数会指向那个 `for` 循环。
### 3. 土办法:二分注释
把可疑代码注释掉,或者把 10 亿改成 1000 试试:
```js
for (let i = 0; i < 1000; i++) { /* ... */ }
```
如果不卡了,说明就是循环次数太大。
---
## 二、怎么修
不要在主线程同步跑这种大循环。
### 方案 A:时间分片,让页面能喘气
```js
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));
}
}
```
用法:
```js
await runInChunks(1000000000, 1000000, (done, total) => {
console.log(`${done}/${total}`);
});
```
这样每跑 100 万次就让出一次主线程,页面不会卡死。
注意:`async/await` 本身不能让出主线程,必须用 `setTimeout` 这类宏任务。
---
### 方案 B:纯计算丢 Web Worker
如果循环里是纯计算,直接丢给 Worker,主线程完全不阻塞。
```js
// worker.js
self.onmessage = function (e) {
let sum = 0;
for (let i = 0; i < e.data; i++) {
sum += i;
}
self.postMessage(sum);
};
```
```js
// main.js
const worker = new Worker('worker.js');
worker.postMessage(1000000000);
worker.onmessage = e => {
console.log('结果:', e.data);
};
```
页面该怎么操作还怎么操作,不会卡。
---
## 三、React / Vue 配合
点击后设置 loading,禁用按钮,防止用户重复点。
### React
```js
const [loading, setLoading] = useState(false);
async function handleClick() {
setLoading(true);
await runInChunks(1000000000, 1000000);
setLoading(false);
}
```
```jsx
<button disabled={loading} onClick={handleClick}>
{loading ? '处理中' : '开始'}
</button>
```
### Vue
```js
const loading = ref(false);
async function onClick() {
loading.value = true;
await runInChunks(1000000000, 1000000);
loading.value = false;
}
```
```vue
<button :disabled="loading" @click="onClick">
{{ loading ? '处理中' : '开始' }}
</button>
```
---
如果这段循环只是测试代码,直接删掉或者改小数字。10 亿次空转本身没有实际意义。
点左边「开工 · 直接出成品」,出一份你自己的版本(文字免费)