JavaScript内存泄漏源于活引用阻止GC回收,主因包括未清理的setInterval(闭包捕获大对象)、未解绑的DOM事件监听器、闭包意外持有大数据,需主动clearInterval、removeEventListener及合理管理闭包引用。
JavaScript 的内存管理不是“不用管”,而是“自动回收 + 人为兜底”。V8 引擎会定期执行垃圾回收(GC),但只要你的代码里还存在**活的引用**,哪怕逻辑上已经不需要,GC 就不会释放它——这就是内存泄漏的根源。
setInterval 不清理就会吃光内存定时器本身很小,但它持有的回调函数会捕获整个闭包作用域。如果回调里引用了 DOM 元素、大型数组或组件实例,这些对象就一直被“拽住”无法回收。
setInterval(() => this.setState(...), 1000),却没在 useEffect 清理函数或 componentWillUnmount 中调用 clearInterval
this,而 this 指向一个长生命周期对象(比如类实例)clearInterval(timerId);对一次性任务,优先用 setTimeout 或 { once: true } 事件监听addEventListener 绑了不解,DOM 就变“僵尸”移除一个 DOM 节点(el.remove() 或 parent.removeChild(el))并不等于清除了所有对它的引用。只要还有事件监听器指向它,它和关联的 JS 对象(包括闭包里的变量)就还在内存里。
click 监听器,但销毁列表时只删了 DOM,没调用 removeEventListener
container.addEventListener('click', handler)),只绑一次,靠 event.target 分发闭包本身无害,问题出在它无意中长期持有了不该持有的数据。比如一个返回函数的工厂方法,内部生成了百万级数组,又通过闭包暴露出去——哪怕外部只调用一次,这个数组也一直活着。
function makeProcessor() { const bigData = new Array(1e6).fill(0); return () => console.log(bigData.length); },调用后 bigData 永远不释放closure = null)WeakMap 存储,它不阻止 GC,对象被移除后自动失效最易被忽略的一点:内存泄漏往往不是单点问题,而是多个小引用叠加的结果——比如一个未清理的定时器 + 一个残留的事件监听器 + 一个闭包里存着的 docume 结果,三者合力就把一个 DOM 节点锁死在内存里。排查时别只盯一处,要用 Chrome DevTools 的 Heap Snapshot 对比“操作前后”的保留树(Retaining Tree),看谁在拽着它不放。