浏览器内存泄漏
1. 概念
JavaScript 拥有垃圾回收机制,会自动清理不再被任何变量引用的对象。但如果某些本该被销毁的无用对象,由于代码逻辑原因仍然被其他的长期存活对象引用着,垃圾回收器就无法回收它们。随着这种对象逐渐累积,最终会耗尽内存,导致页面卡顿或崩溃。这就是内存泄漏。
2. 常见场景
(1) 未清理的定时器
在组件卸载或任务结束后,如果没有调用 clearInterval,定时器的回调函数及其闭包引用的外部变量将一直驻留在内存中。
useEffect(() => {
const data = new Array(10000);
const timer = setInterval(() => {
console.log(data.length);
}, 1000);
// 必须 return () => clearInterval(timer);
}, []);
(2) 游离的 DOM 引用
当一个 DOM 节点从页面文档树中被移除,但仍被某个全局变量或常驻内存的 JavaScript 变量引用着,这个节点及其所有子节点就成了游离 DOM,无法被回收。
let detachedNodes = null;
function removeElement() {
const node = document.getElementById('box');
document.body.removeChild(node);
detachedNodes = node; // 变量存有引用,导致 DOM 节点无法回收
}
(3) 未解绑的全局事件
在 window 或 document 上绑定的事件监听器(如 scroll、resize),在依赖它们的组件销毁时,如果未及时通过 removeEventListener 解绑,回调函数闭包将继续存活。
(4) 第三方库的未销毁实例
部分第三方库在初始化时会涉及画布生成或绑定全局事件,在路由切换销毁特定组件时,需要显式手工调用库提供的销毁解绑方法。
3. 定位内存泄漏
可以使用开发者工具的 Memory 面板进行排查:
-
Heap Snapshot(堆快照比对):
- 在进行怀疑会引发泄漏的操作前,拍下第一个快照。
- 反复执行操作并在预期资源该被释放后,拍下第二个快照。
- 使用对比视图,查看两次快照间的增量对象。如果存在名称带有 Detached HTML 标签元素的对象,这通常指向了明显的游离 DOM 泄漏。
-
Allocation instrumentation on timeline(按时间线分配记录):
- 记录一段时间内的内存分配行为。如果在经历过垃圾回收后,内存柱状图依然呈阶梯式持续上升,可以点击对应的时间节点,查看在持续分配并未被释放的对象来源。
4. 优化建议
- 养成良好的组件生命周期管理习惯,及时清除定时器、解绑全局事件。
- 对于用作某些弱引用的复杂数据结构,可以考虑使用
WeakMap 或 WeakSet。它们的键是弱引用,当外部不再持有该对象的强引用时,垃圾回收机制会自动回收存储的数据。