
下午茶时间,我给隔壁的后端老哥发了个链接。
他毫无防备地点开了。
三秒钟后,我听到他那台 16G 内存的 MacBook Pro 风扇开始狂啸,仿佛要起飞🤣。
五秒钟后,他的鼠标指针变成了彩虹圈,卡在屏幕中间动弹不得。
十秒钟后,Chrome 惨叫一声,弹出了那个让无数前端人闻风丧胆的页面------(崩溃啦)。

后端老哥过来问我:你刚才发的是什么病毒?!😡
我淡定地喝了一口咖啡:没有病毒,只是一行 JavaScript 代码而已。
今天,我就带大家拆解一下这个崩溃背后的原理。这不仅是个恶作剧,更是深入理解 V8 引擎内存机制 的绝佳机会。
这行代码长什么样呢?
⚠️ 高能预警:请勿在未保存工作的浏览器标签页中运行以下代码!
⚠️ 高能预警:请勿在未保存工作的浏览器标签页中运行以下代码!
⚠️ 高能预警:请勿在未保存工作的浏览器标签页中运行以下代码!
重要的事,先说三遍😃
如果你想让浏览器瞬间暴毙,其实不需要什么复杂的黑客技术。最简单粗暴的方式,就是 堆溢出 (Heap Overflow)。
核心代码只有这一行(为了方便展示,我稍微展开一点):
JavaScript
// 就是它!
const boom = () => {
window.leakArr = [];
setInterval(() => {
// 分配 20MB 的 TypedArray
window.leakArr.push(new Uint8Array(20 * 1024 * 1024));
}, 10);
};
boom();
原理极其简单:
- 我们开了一个
setInterval定时器,每 10 毫秒执行一次。 new Uint8Array(20 * 1024 * 1024)会创建一个约 20MB 的 TypedArray。- 最关键的一步:我们将这个TypedArray
push进了一个全局变量window.leakArr中。
因为 leakArr 是挂在 window 上的,浏览器会认为:这个数据用户还要用,我不能回收!
于是,GC(垃圾回收器)不敢动它。
内存就像被注水一样瞬间飙升。
1秒钟增加 1GB,几秒钟后,Chrome 的 V8 引擎就会因为达到 Heap Limit(堆上限) 而崩溃。

浏览器是怎么死的?
很多兄弟会问:我电脑 32G 内存,这才哪到哪?为什么浏览器这么脆?
这里就要讲到 Node.js 和 Chrome V8 的一个限制。
V8 的内存限制(Heap Limit)
Chrome 并不是你有多少物理内存,它就敢用多少。
为了防止网页占用过多资源导致系统卡死,V8 对每个页面的 JS 堆内存是有硬性限制的。
- 在 64 位系统下,默认限制通常在 1.4GB - 4GB 之间(取决于 Chrome 版本和设备)。
当你那行代码疯狂申请内存时,V8 的处理是这样的:
- 第 1 秒:内存占用 500MB。GC:还行,能撑住。
- 第 2 秒:内存占用 1.5GB。GC:卧槽?快满了!赶紧 Full GC(全量回收)一下!
- 第 2.1 秒 :GC 发现
leakArr被window引用着,一个字节都回收不掉。 - 第 3 秒 :内存突破限制。V8:毁灭吧,累了😖。 -> Crash。
主线程阻塞 (The UI Freeze)
在崩溃前,为什么鼠标会卡死?
因为 JS 是单线程的。
当内存快满时,GC 会疯狂介入,试图腾出空间。GC 的执行是会阻塞主线程的。
CPU 都在忙着做垃圾回收,根本没空去响应你的鼠标移动和页面渲染。
你可能每天都在写 Bug🤔
上面的代码是明目张胆的抢劫,但在我们的日常业务代码中,更多的是 慢性偷窃 ------也就是内存泄漏 (Memory Leak)。
虽然你不会写出 while(true),但以下这几种操作,和上面的性质是一样的:
忘记清除的定时器 / 监听器
JavaScript
// React 组件 useEffect
useEffect(() => {
const timer = setInterval(() => {
console.log('Fetching data...');
// 闭包引用了大量数据
}, 1000);
// 致命错误:忘记写 return () => clearInterval(timer);
}, []);
如果你在 SPA(单页应用)里频繁切换路由,这个组件虽然卸载了,但 timer 还在跑,引用的数据还在内存里。切几十次路由,页面就卡成 PPT 了。
游离的 DOM 节点
JavaScript
let detachedNodes = [];
function create() {
const ul = document.createElement('ul');
for (let i = 0; i < 1000; i++) {
ul.appendChild(document.createElement('li'));
}
// 虽然这个 ul 没有插入到 document.body 里
// 但它被全局变量 detachedNodes 引用着,无法回收!
detachedNodes.push(ul);
}
很多图表库(ECharts 等)如果销毁时不调用 dispose(),就会留下这种内存幽灵。
3. 闭包陷阱
JavaScript
function outer() {
const heavyData = new Array(1000000).join('*'); // 大数据
return function inner() {
console.log('I am keeping heavyData alive!');
// 虽然 inner 只用到了 console,但因为它在 outer 内部
// 有些旧引擎可能会连带把 heavyData 一起保留在闭包上下文中
}
}
如何防止呢?
如果不幸遇到了页面越用越卡,怎么排查是不是内存泄露?
Chrome DevTools 是你最好的排查工具:
-
打开 Performance Monitor(性能监视器):
F12->Esc-> 选择Performance monitor。- 盯着 JS 堆 和 DOM 节点。
- 如果你做过一遍 GC(点击 Memory 面板的小垃圾桶),这两个数值只增不减,恭喜你,漏了。

-
Memory 面板抓快照:
- 录制 Heap Snapshot。
- 搜索
Detached,看看有没有红色的游离 DOM 节点。 - 或者对比两次快照(Allocation instrumentation on timeline),看看到底是哪个对象在疯狂增长。

那行让浏览器崩溃的代码,大家玩玩就好,千万别写到生产环境里(除非你渴望被运维提着刀追杀🤣)。
前端工程师的价值,不只是把页面画出来,更是要保证页面在长时间运行下依然丝滑。
下次再遇到页面卡顿,别光顾着怪网络慢,打开控制台看看,说不定是你写的某个内存泄漏代码正在悄悄吞噬用户的内存。
