返回首页

JS 闭包中的内存泄漏:如何修复

文章分解了 JavaScript 闭包中内存泄漏的机制,附带代码示例和 DevTools。描述的解决方案:重置引用、移除处理程序、带有弱引用的 WeakMap。实用的诊断说明。

JS 闭包中的内存泄漏:WeakMap 和解决方案
Advertisement 728x90

JavaScript 闭包内存泄漏避免指南

JavaScript 闭包会让对象在内存中存活,只要闭包函数还有引用。当对象不再需要时,如果被闭包捕获,垃圾回收器就无法释放它,导致内存泄漏。

来看一个典型例子,涉及大对象:

let bigData = {
  id: 42,
  data: new Array(1000000).fill('Some data')
}

function handler(data) {
  return function() {
    console.log(data.id);
    debugger;
  }
}

const runHandler = handler(bigData);
bigData = null;
runHandler();

内部函数只用了 data.id,但闭包捕获了整个 bigData 对象。在 DevTools 断点处,你会看到对象仍滞留在内存中。

Google AdInline article slot

闭包中使用 eval() 的问题

eval() 函数会加剧泄漏,因为引擎会把所有外部变量加入闭包,无论是否使用:

let hello = 'Hello';
let userName = 'Vasya';

setTimeout(function () {
  debugger;
  return eval('console.log("Hello from Vasya!")');
}, 1000);

在 DevTools 中,闭包包含了 hellouserName,尽管 eval() 没引用它们。这是由于 eval() 内代码的不确定性。生产环境中避免使用 eval()

防止内存泄漏的方法

强制重置数据

使用后重置捕获的数据:

Google AdInline article slot
function runCalc(buttonId) {
  let bigData = new Array(1000000).fill('Something');
  const button = document.getElementById(buttonId);

  button.addEventListener('click', function onClick() {
    console.log('Processed items:', bigData.length);
    bigData = null;
  });
}

bigData 赋值为 null 释放闭包内的引用,让垃圾回收器移除对象。

正确移除事件处理器

移除 DOM 元素时,事件处理器是常见泄漏源:

function clickSetup(btn, message) {
  const bigData = new Array(1000000).fill(message);

  function handler() {
    console.log(message, bigData.length);
    btn.remove();
    btn.removeEventListener('click', handler);
  }

  btn.addEventListener('click', handler);
}

命名函数 handler 可以随元素一起移除。匿名箭头函数无法这样移除。

Google AdInline article slot

WeakMap 实现弱引用

WeakMap 通过对对象键的弱引用,解决捕获大对象的问题。

键必须是对象

const weak = new WeakMap();
const key = {};
weak.set(key, 'Vasya was here!');
console.log(weak.get(key));
const anotherKey = {};
console.log(weak.get(anotherKey)); // undefined

键必须是对象或非原始类型。通过引用访问 get(key)

弱引用与垃圾回收

WeakMap 中的引用不会阻止垃圾回收:

const weak = new WeakMap();
let userKey1 = {id: 1};
weak.set(userKey1, 'Vasya');
userKey1 = null;

userKey1 置为 null 后,对象和值会被垃圾回收。

无迭代器

没有 sizeforEachkeys()。仅基本方法:

  • set(key, value) — 设置
  • get(key) — 获取
  • has(key) — 检查
  • delete(key) — 删除

无迭代器避免垃圾回收时的不可预测性。

在闭包中使用 WeakMap

使用 WeakMap 的闭包不会强持有对象:

function createProcessor() {
  const weakMap = new WeakMap();

  return function process(obj) {
    weakMap.set(obj, obj.value);
    debugger;
    return weakMap.get(obj);
  }
}

const processor = createProcessor();

let bigData = {
  value: 21,
  hugeArray: new Array(1000000).fill('*')
};

console.log(processor(bigData));
bigData = null;

let bigData2 = {
  value: 42,
  hugeArray: new Array(1000000).fill('1')
};

console.log(processor(bigData2));

垃圾回收后 DevTools 中只剩 bigData2WeakMap 实现自动内存清理。

验证步骤:

  • process() 中设置断点。
  • 单步执行代码 (F10)。
  • bigData = null 后,切换到 Memory 标签。
  • 点击按钮触发垃圾回收。
  • 继续执行 — 只剩当前键。

关键要点

  • 闭包捕获整个对象,即使只用一个属性。
  • eval() 将所有外部变量加入闭包。
  • 命名事件处理器可通过 removeEventListener 移除。
  • WeakMap 对对象键使用弱引用。
  • WeakMap 无迭代器,确保垃圾回收可预测。

— Editorial Team

Advertisement 728x90

继续阅读