JS 内存泄漏的几种情况

1. 什么是内存泄漏

内存泄漏(Memory Leak)是指由于疏忽或错误,程序未能释放已经不再使用的内存。

并非指内存在物理上消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。

对于持续运行的服务进程,必须及时释放不再用到的内存,否则内存占用越来越高,轻则影响系统性能,重则导致进程崩溃。

2. 垃圾回收机制的两种典型策略

2.1 标记清除(Mark-Sweep,主流)

JS 最常用的 GC 机制。

  • 当变量进入执行环境时,标记为"进入环境"。进入环境的变量所占用的内存不能释放。
  • 当变量离开环境时,标记为"离开环境"。
  • GC 运行时先标记内存中所有变量,然后把所有在上下文中的变量、以及被在上下文中的变量引用的变量的标记去掉。
  • 之后再被加上标记的变量就是待删除的(任何上下文中的变量都访问不到它们了)。
  • 最后 GC 做一次内存清理,销毁带标记的所有值并回收内存。

2.2 引用计数(Reference Counting)

引擎维护一张"引用表",记录每个值的引用次数。如果引用次数为 0,就释放内存。

缺陷:循环引用 时引用数永远不为 0,会导致内存泄漏。IE 早期 COM 对象等问题就是如此,现代 V8 主要使用标记清除。

3. 常见内存泄漏情况

3.1 意外的全局变量

function foo() {
  bar = 'something'; // 未声明变量,相当于 window.bar = 'something'
}
foo();
// foo 执行完毕,本应销毁的 bar 却永久保留在内存中

避免:使用严格模式 'use strict',或使用 let / const 声明变量。

3.2 定时器未清除

const timer = setInterval(() => {
  // ...
}, 1000);
// 组件销毁时忘记 clearInterval(timer),timer 会持续引用闭包变量

3.3 闭包

function outer() {
  const big = new Array(10000);
  return function () { return big[0]; };
}
const fn = outer();
// big 通过闭包被 fn 持续引用,无法释放

3.4 未清理的 DOM 引用

const btn = document.getElementById('btn');
btn.onclick = null; // 移除事件
// 但 JS 中仍保留 btn 引用,且 btn 元素也被其他对象引用,无法回收

3.5 未移除的事件监听

window.addEventListener('resize', onResize);
// 组件销毁时需 removeEventListener('resize', onResize),否则回调函数及其闭包被持续保留

补充:调试内存泄漏常用 Chrome DevTools 的 Memory 面板:录制堆快照,对比不同时间点的对象数量增长;用 Performance 面板观察堆曲线是否单调上升。

来源整理自:vue3js.cn 面试官系列