常见内存泄漏

1. 什么是内存泄漏?

内存泄漏 是指程序运行时,不再使用的内存空间没有被及时释放,导致系统可用内存不断减少。

在浏览器中,内存泄漏会导致页面越来越卡,最终崩溃。

2. 常见泄漏原因 (高频面试题)

(1) 全局变量

意外创建的全局变量是内存泄漏最常见的原因。

// ❌ 忘记声明变量
function foo() {
  bar = "This is a global variable"; // 这里实际上是 window.bar
}

// ❌ 意外的 this 指向
function foo() {
  this.bar = "global"; // 这里的 this 是 window
}

对策:使用 use strict 模式,严格检查变量声明。避免在全局作用域挂载大量数据。

(2) 闭包

最经典的泄漏原因。闭包会引用外部函数的变量,导致这些变量无法被 GC 回收。

var theThing = null;
var replaceThing = function () {
  var originalThing = theThing;
  // 这里的 unused 函数引用了 originalThing,即使它从未被调用
  var unused = function () {
    if (originalThing)
      console.log("hi");
  };

  theThing = {
    longStr: new Array(1000000).join('*'),
    someMethod: function () {
      console.log(someMessage);
    }
  };
};
// 循环调用 replaceThing 会导致 huge memory leak

(注:这是 Meteor 前端框架早期的经典 bug 示例)

对策:在不需闭包时,解绑闭包函数或者将引用变量赋值为 null

(3) DOM 引用的失效

当我们保存了一个 DOM 节点的引用,后来从文档中把这个 DOM 删除了,但 JS 中的引用还在,这部分内存(整个 DOM 树)就无法回收。

var elements = {
  button: document.getElementById('button')
};

function removeButton() {
  document.body.removeChild(document.getElementById('button'));
  // 虽然移除了 DOM,但 elements.button 依然引用着它,导致内存无法回收
}

对策:移除 DOM 后,记得把对应的 JS 变量置为 null (elements.button = null)。

(4) 定时器

var someResource = getData();
setInterval(function() {
  var node = document.getElementById('Node');
  if(node) {
    node.innerHTML = JSON.stringify(someResource);
  }
}, 1000);

如果节点 Node 被移除了,但定时器还在跑,someResource 就永远无法回收。

对策:组件卸载或页面跳转时 clearInterval / clearTimeout

(5) 事件监听器

组件挂载时添加了 window.addEventListener,但在卸载时忘记 removeEventListener

对策:务必在 beforeUnmountuseEffect 的 cleanup 函数中移除监听器。

3. 排查工具

使用 Chrome DevTools 的 Memory Panel

  1. Timeline: 录制一段时间的操作,看内存曲线是否只升不降。
  2. Heap Snapshot: 拍快照,对比不同时间点的内存快照,查找未释放对象。
  3. Allocation Profile: 查看 JS 函数分配内存的热点。