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.
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:
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í.
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)— establecerget(key)— obtenerhas(key)— verificardelete(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. WeakMapusa referencias débiles a claves de objeto.- Sin iteradores en
WeakMappara una recolección de basura predecible.
— Editorial Team
Aún no hay comentarios.