Volver al inicio

Fugas de memoria en cierres de JS: cómo solucionarlas

El artículo desglosa los mecanismos de las fugas de memoria en cierres de JavaScript con ejemplos de código y DevTools. Soluciones descritas: restableciendo referencias, eliminando manejadores, WeakMap con referencias débiles. Instrucciones prácticas de diagnósticos.

Fugas de memoria en cierres de JS: WeakMap y soluciones
Advertisement 728x90

Cómo evitar fugas de memoria en cierres de JavaScript

Los cierres de JavaScript mantienen los objetos vivos en memoria mientras exista una referencia a la función de cierre. Esto provoca fugas cuando un objeto ya no se necesita, pero el recolector de basura no puede liberarlo porque está capturado en el cierre.

Considera un caso típico con un objeto grande:

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

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

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

La función interna solo usa data.id, pero el cierre captura todo el objeto bigData. En DevTools, en el punto de interrupción, verás que el objeto sigue en memoria.

Google AdInline article slot

Problemas con eval() en cierres

La función eval() agrava las fugas porque el motor añade todas las variables externas al cierre, sin importar si se usan:

let saludo = '¡Hola!';
let nombreUsuario = 'Juan';

setTimeout(function () {
  debugger;
  return eval('console.log("¡Hola de Juan!")');
}, 1000);

En DevTools, el cierre incluye saludo y nombreUsuario, aunque eval() no los referencie. Esto ocurre por la incertidumbre del código dentro de eval(). Evita eval() en producción.

Métodos para prevenir fugas

Forzar la limpieza de datos

Limpia los datos capturados después de usarlos:

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

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

Asignar null libera la referencia dentro del cierre, permitiendo que el recolector de basura elimine el objeto.

Eliminar correctamente los manejadores de eventos

Los manejadores de eventos son una fuente común de fugas al eliminar elementos 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);
}

Una función con nombre handler se puede eliminar junto con el elemento. Las funciones flecha anónimas no se pueden eliminar así.

Google AdInline article slot

WeakMap para referencias débiles

WeakMap resuelve el problema de capturar objetos grandes usando referencias débiles a las claves de objeto.

Las claves deben ser objetos

const weak = new WeakMap();
const key = {};
weak.set(key, '¡Juan estuvo aquí!');
console.log(weak.get(key));
const anotherKey = {};
console.log(weak.get(anotherKey)); // undefined

Las claves deben ser objetos o no primitivos. Acceso vía get(key) por referencia.

Referencias débiles y recolección de basura

Las referencias en WeakMap no impiden la recolección de basura:

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

Tras anular userKey1, el objeto y su valor se recolectan como basura.

Sin iteradores

No hay size, forEach, keys(). Solo métodos básicos:

  • set(key, value) — establecer
  • get(key) — obtener
  • has(key) — verificar
  • delete(key) — eliminar

Sin iteradores para evitar impredecibilidad durante la recolección de basura.

Usar WeakMap en cierres

Un cierre con WeakMap no retiene objetos con fuerza:

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));

En DevTools tras la recolección de basura, solo queda bigData2. WeakMap permite una limpieza automática de memoria.

Para verificar:

  • Pon un punto de interrupción en process().
  • Avanza paso a paso (F10).
  • Tras bigData = null, ve a la pestaña Memory.
  • Activa la recolección de basura con el botón.
  • Continúa — solo quedan las claves actuales.

Lecciones clave

  • Los cierres capturan objetos enteros, aunque solo se use una propiedad.
  • eval() añade todas las variables externas al cierre.
  • Los manejadores de eventos con nombre se eliminan con removeEventListener.
  • WeakMap usa referencias débiles a claves de objeto.
  • Sin iteradores en WeakMap para una recolección de basura predecible.

— Editorial Team

Advertisement 728x90

Leer después