38、说说 JavaScript 中内存泄漏的几种情况?

张开发
2026/4/20 11:41:49 15 分钟阅读

分享文章

38、说说 JavaScript 中内存泄漏的几种情况?
一、什么是内存泄漏内存泄漏指的是程序中已经不再需要使用的内存因为仍然存在引用导致垃圾回收机制无法释放最终造成内存持续增长。在 JavaScript 里虽然有垃圾回收机制但GC 只能回收“不可达对象”。如果某些对象逻辑上已经没用了但仍然被引用着那么这些对象就不会被释放这就形成了内存泄漏。面试里可以先这样概括JavaScript 的内存泄漏本质上不是“内存回收失效”而是“本该释放的对象仍然可达”导致 GC 没法回收。常见原因通常是意外的全局变量、没有清理的定时器和事件监听、闭包引用、DOM 引用未释放、缓存使用不当等。这句很适合作为开头。二、JavaScript 中常见的内存泄漏场景1意外创建的全局变量如果变量没有用let、const、var声明或者不小心挂到window上就可能变成全局变量。全局变量生命周期很长不容易被释放。示例function foo() { name Tom // 没有声明变成全局变量 } foo()或者window.cacheData new Array(1000000).fill(data)为什么会泄漏因为全局对象在页面生命周期内通常一直存在被它引用的数据也就一直无法回收。如何避免开启严格模式使用let/const避免随意往window上挂数据2定时器没有清理setInterval、setTimeout如果没有及时清理回调里又引用了外部对象就可能导致这些对象长期无法释放。示例function start() { const data new Array(100000).fill(memory) setInterval(() { console.log(data.length) }, 1000) } start()这里data一直被定时器回调引用。如何避免组件卸载时clearInterval/clearTimeout对不再需要的轮询及时停止3事件监听没有移除给 DOM、window、document 绑定的事件如果组件销毁或元素移除时没有解绑事件处理函数仍然会持有相关引用。示例function bindEvent() { const btn document.getElementById(btn) btn.addEventListener(click, function () { console.log(clicked) }) }如果这个按钮对应的逻辑被移除但监听器没清掉就可能有泄漏风险尤其是在频繁创建销毁的场景中。更典型的例子window.addEventListener(resize, this.handleResize)组件销毁时如果不移除window.removeEventListener(resize, this.handleResize)就容易出问题。如何避免组件销毁时解绑事件使用事件委托减少绑定数量在框架生命周期钩子中统一清理4闭包导致对象长期被引用闭包本身不是问题但如果闭包长期存在并引用了本来应该释放的大对象就可能造成内存泄漏。示例function outer() { const hugeData new Array(1000000).fill(x) return function () { console.log(hugeData[0]) } } const fn outer()只要fn还在hugeData就不会释放。说明面试时最好说清楚闭包不是一定会导致内存泄漏而是不合理使用闭包让无用数据持续被引用才会引发泄漏。这是一个很加分的表达。5DOM 引用未释放如果某个 DOM 节点已经从页面移除但 JS 中还保留着对它的引用那么相关内存就可能无法释放。示例let div document.getElementById(box) document.body.removeChild(div) // 这里 div 变量仍然引用这个节点如何避免div null尤其是老项目、大量动态创建 DOM 的场景中这类问题比较典型。6缓存使用不当缓存本质上是“用空间换时间”如果只存不删就容易造成内存持续增长。示例const cache {} function memoize(key, value) { cache[key] value }如果 key 越来越多又没有清理策略这个缓存就会不断膨胀。常见场景接口缓存计算结果缓存全局 Map / Set 存储对象单页应用中的页面状态缓存如何避免设置最大容量设置过期时间 TTL使用 LRU 策略对对象 key 可以考虑 WeakMap7未清理的 Promise / 异步回调严格来说 Promise 本身不一定直接造成泄漏但如果异步任务长期不结束或者回调链中引用了大量上下文数据也可能导致对象长时间滞留内存。场景请求一直 pending页面卸载了但异步回调还持有组件实例WebSocket / EventSource 长连接没有关闭如何避免请求取消AbortController页面销毁时断开连接避免在异步回调里长期持有无用对象8分离 DOMDetached DOM这是前端面试里比较专业的点。是什么DOM 节点已经从文档树中移除了但 JS 里仍然持有对它的引用这种节点叫Detached DOM。示例let node document.createElement(div) document.body.appendChild(node) document.body.removeChild(node) // node 还在被引用 console.log(node)危害如果这类节点很多且节点结构复杂会占用较多内存。9控制台引用 / 调试代码未清理开发时我们有时会window.temp bigObject console.log(bigObject)某些情况下调试工具或手动挂到全局对象上的数据会导致对象迟迟不被释放。这个点不是最核心但说出来会显得你有排查经验。三、前端框架中的常见内存泄漏场景如果是前端岗位最好再补这一层。1Vue / React 组件卸载时未清理副作用比如定时器没清全局事件没解绑WebSocket 没关闭订阅没取消React 中useEffect里忘记返回 cleanup闭包引用旧 stateVue 中beforeUnmount/unmounted没做清理2组件缓存过多比如 keep-alive、页面缓存策略不合理导致大量页面实例长期不释放。3第三方库实例未销毁比如地图实例图表实例ECharts编辑器实例视频播放器实例如果组件销毁时不执行destroy()/dispose()就可能泄漏。这个点在项目里特别常见很加分。四、如何排查内存泄漏这是“精彩回答”的关键。很多人只会说场景不会说排查。1使用 Chrome DevTools 的 Memory 面板常见方式Heap snapshot拍内存快照对比对象数量变化Allocation instrumentation on timeline看内存分配趋势Detached DOM tree查看是否存在被分离但仍被引用的 DOM2观察 Performance 面板中的内存变化如果页面操作结束后内存不下降且多次操作后持续抬高通常说明有泄漏风险。3多次进入/退出页面做对比比如进入页面操作一轮离开页面重复几次如果内存持续上涨且不回落就要重点排查。4查看保留路径Retainers在堆快照中可以查看某个对象为什么没有被回收看是谁在引用它。这是很有经验感的一句话真正排查内存泄漏时关键不是看“对象大不大”而是看“为什么它还可达”也就是它的引用链。五、如何避免内存泄漏可以总结成这几条1减少全局变量用模块化管理状态避免临时数据挂在window2及时清理副作用清除定时器移除事件监听取消订阅关闭 WebSocket / 观察器3谨慎使用闭包不要让闭包长期持有大对象无用引用及时释放4管理缓存生命周期TTLLRUWeakMap5DOM 销毁后释放引用尤其是动态节点、列表节点、第三方组件容器6组件销毁时统一做 cleanup这点在 React/Vue 中很重要。六、面试怎么回答更精彩你可以直接按这个结构说标准版回答JavaScript 中的内存泄漏本质上是一些本来已经不需要的对象因为仍然被引用导致垃圾回收机制无法释放。所以它不是 GC 失效而是对象依然“可达”。常见场景有几个第一意外的全局变量因为全局对象生命周期很长第二没有清理的定时器和事件监听它们会一直持有外部引用第三闭包使用不当导致大对象被长期引用第四DOM 节点从页面移除了但 JS 里还保留引用也就是 Detached DOM第五缓存使用不当比如全局 Map、对象缓存只增不删第六前端框架里组件卸载时没有清理副作用比如定时器、订阅、WebSocket、第三方实例等。排查内存泄漏时我一般会用 Chrome DevTools 的 Memory 面板做 Heap Snapshot对比多次操作前后的内存变化重点查看对象的 Retainers也就是引用链看它为什么还没有被释放。避免内存泄漏的核心就是减少不必要的全局引用、及时清理副作用、管理好缓存生命周期、DOM 销毁后释放引用以及在组件卸载时统一做 cleanup。这段已经很成熟了。七、如果想更高级可以补一句我觉得判断是不是内存泄漏不能只看内存高不高而要看业务操作结束后内存是否应该回落却没有回落。如果对象逻辑上已经没用了但还一直被引用那才是典型泄漏。这句非常加分。八、精简版面试回答如果时间比较短可以这样答JavaScript 的内存泄漏指的是不再使用的对象仍然被引用导致垃圾回收无法释放。常见情况包括意外全局变量、未清理的定时器、未移除的事件监听、闭包长期持有大对象、Detached DOM、缓存只增不删以及组件卸载时没有清理副作用。排查时可以用 Chrome DevTools 的 Memory 面板做堆快照对比前后变化并查看对象的引用链。预防的关键是及时清理定时器和事件、减少全局引用、合理管理缓存、DOM 删除后释放引用以及在框架组件卸载时做好 cleanup。九、一句话总结面试官真正想听的是你是否理解内存泄漏的本质对象仍然可达你是否知道常见泄漏场景你有没有排查经验你是否具备工程化预防意识

更多文章