JavaScript内存泄漏指本该被GC释放的对象因隐式引用无法回收,导致SPA等长期运行应用内存持续上涨、卡顿甚至崩溃;常见原因包括闭包保留DOM引用、未清理定时器/事件监听器、全局变量、大对象资源未释放及跨模块隐式引用链。

JavaScript 内存泄漏不是“变量没及时 delete”那么简单,而是指本该被垃圾回收器(GC)释放的对象,因为某些隐式引用持续存在,导致无法回收——它不会立刻崩,但长期运行的单页应用(SPA)、定时任务、事件监听密集场景下,内存占用会持续上涨,最终卡顿甚至崩溃。
闭包意外保留 DOM 引用
常见于事件回调中捕获了父作用域的 DOM 节点,而该节点已被移除,但闭包仍持引用,GC 无法清理整个子树。
- 避免在闭包中直接保存对已卸载 DOM 元素的引用,改用
id或dataset等轻量标识 - 手动解绑事件前,先清空闭包内对 DOM 的强引用(例如设为
null) - 使用
WeakMap存储与 DOM 元素关联的状态,避免强引用延长生命周期
const elementCache = new WeakMap();
elementCache.set(document.getElementById('btn'), { clicked: true }); // 安全:DOM 被移除后自动清理未清理的定时器和事件监听器
setInterval、addEventListener 是泄漏高发区。组件销毁(如 React 卸载、Vue beforeUnmount)后,若忘记清除,回调函数及其闭包持续存活,连带捕获的所有对象都无法回收。
- 所有
setInterval/setTimeout必须配对clearInterval/clearTimeout,且确保在组件销毁时执行 - 用
addEventListener时,优先使用{ once: true }选项;长期监听务必保存handler引用以便后续removeEventListener - 避免在监听器里直接写匿名函数——无法精确移除,也阻碍调试
function handleClick() { /* ... */ }
element.addEventListener('click', handleClick);
// 销毁时:
element.removeEventListener('click', handleClick);全局变量和意外挂载
忘记 var/let/const 声明变量,或误将临时对象赋值给 window、globalThis,会导致对象永远无法被 GC。
立即学习“Java免费学习笔记(深入)”;
- 开启严格模式(
'use strict'),让漏声明变量直接报错,而不是静默挂到全局 - 避免向
window添加非必需属性,尤其缓存数据、大型数组或 DOM 集合 - 检查控制台的
console.log(this)是否意外指向window,排查 this 绑定错误导致的隐式全局写入
频繁创建未释放的大对象(如 canvas、WebGL、Blob)
这类资源不归 JS 垃圾回收器直接管理,但其 JS 封装对象(如 CanvasRenderingContext2D、Blob)若被持有,会阻止底层资源释放。
-
canvas.getContext('2d')返回的上下文对象应随 canvas 生命周期结束而丢弃;重用 canvas 时调用context.reset()(如支持)或重建 -
Blob使用完立即调用URL.revokeObjectURL(url),否则浏览器可能长期保留内存中的二进制数据 - WebGL 纹理、缓冲区需显式调用
gl.deleteTexture()、gl.deleteBuffer(),不能只靠 JS 对象置 null
真正难排查的是跨模块、跨生命周期的隐式引用链,比如一个被遗忘的 Promise 拒绝后未处理,其闭包里存着整个表单数据;或者第三方库内部缓存了你传入的回调并长期持有。用 Chrome DevTools 的 Memory 面板录制堆快照,按“Retained Size”排序,再看 “Retainers” 链路,比猜更可靠。











