内存泄漏的本质是“本该释放的对象因意外强引用仍可达”,导致GC无法回收;典型场景包括意外全局变量、未清理定时器、未卸载事件监听器、闭包过度捕获大对象。

内存泄漏的本质不是“内存坏了”,而是“东西明明不用了,却没人告诉系统可以收走”。JavaScript 有自动垃圾回收(GC),但它只回收那些完全没人引用的对象。只要还有一个活的引用连着它,GC 就会认为“这人可能还要用”,死死留着——哪怕你逻辑上早就不需要它了。
就像图书馆借书没还
你去图书馆借了一本书(创建一个大数组或对象),看完后随手放在桌上(函数执行完,局部变量本该消失)。但你不小心把借书卡塞进了另一个长期有效的登记簿里(比如全局变量、定时器回调、事件监听器、闭包),管理员(GC)一查登记簿,发现“这本书还在借阅中”,就绝不会把它下架归还。借得越多,桌上堆得越满,最后连走路都困难(页面卡顿、崩溃)。
四种最典型的“忘还书”方式
• 全局变量乱挂:没用 let/const 声明,直接写 data = new Array(1e6),它就挂在 window 上,页面不关,它就不走。
• 定时器没清理:setInterval(() => console.log(obj.name), 1000) 里用了 obj,哪怕后续把 obj = null,定时器还在跑,闭包一直拽着 obj 不放。
• 事件监听器不卸载:给按钮绑了 click 处理函数,组件删了、按钮 DOM 都移除了,但监听器还在 EventEmitter 或 document 上挂着,连带它闭包里的数据全被锁住。
• 闭包过度捕获:函数内部定义了一个大数组,返回的闭包只用其中一个小字段,却把整个数组都“拖”在内存里,因为 GC 看不到“只用一部分”,它只看“有没有引用”。
关键不在“怎么分配”,而在“谁还拿着它”
JS 引擎不靠时间判断对象该不该收,也不靠你喊“我不用了”。它只做一件事:从几个固定起点(如全局对象、正在执行的函数栈)出发,顺着所有引用链一路找过去;凡是能走到的,就留下;走不到的,才回收。 所以泄漏的根本,永远是“不该存在的引用链,还硬生生连着”。


















