Closures en JavaScript: temporizadores, carga diferida y configuración encapsulada

Los closures son una de esas características de JavaScript que a veces suenan abstractas, pero que, bien aprovechadas, pueden simplificar mucho el código. En este artículo vamos a ver tres casos reales donde los closures hacen el trabajo más limpio y mantenible: temporizadores con pausa y reanudación, carga diferida de recursos y un módulo de configuración privado.
1. Temporizador con pausa y reanudación
Imagina que necesitas un cronómetro que puedas iniciar, pausar y reanudar desde cualquier parte de tu aplicación. Sin un closure tendrías que almacenar variables globales o pasar estados entre funciones, lo que aumenta el riesgo de colisiones.
function crearCronometro() {
let start = 0;
let acumulado = 0;
let timerId = null;
function tick() {
const ahora = Date.now();
console.log('Tiempo transcurrido:', (acumulado + ahora - start) / 1000, 's');
timerId = requestAnimationFrame(tick);
}
return {
iniciar() {
if (!timerId) {
start = Date.now();
timerId = requestAnimationFrame(tick);
}
},
pausar() {
if (timerId) {
cancelAnimationFrame(timerId);
acumulado += Date.now() - start;
timerId = null;
}
},
reiniciar() {
cancelAnimationFrame(timerId);
start = Date.now();
acumulado = 0;
timerId = requestAnimationFrame(tick);
}
};
}
const cronometro = crearCronometro();
cronometro.iniciar(); // empieza a contar
// … luego en algún evento
cronometro.pausar(); // se detiene sin perder el tiempo acumulado
// … y más tarde
cronometro.iniciar(); // continúa donde quedó
El objeto devuelto mantiene su propio start, acumulado y timerId. Gracias al closure, esas variables no son accesibles desde fuera, evitando manipulaciones accidentales.
2. Carga diferida (lazy loading) de imágenes
En proyectos con muchas imágenes, cargar todo al inicio afecta al rendimiento. Un closure nos permite crear una función que, la primera vez que se invoque, cargue la imagen y, en llamadas posteriores, devuelva la ya cargada.
function cargarImagenLazy(url) {
let img = null; // variable privada
return function() {
if (img) return Promise.resolve(img); // ya está cargada
return new Promise((resolve, reject) => {
const elemento = new Image();
elemento.onload = () => {
img = elemento; // guardamos la referencia
resolve(img);
};
elemento.onerror = reject;
elemento.src = url;
});
};
}
const getLogo = cargarImagenLazy('/assets/logo.png');
// En cualquier punto de la UI
getLogo().then(img => document.body.appendChild(img));
El closure conserva la variable img entre llamadas, de modo que la imagen sólo se descarga una vez. Es una solución sencilla que no necesita librerías externas.
3. Módulo de configuración privado
Muchas aplicaciones requieren un objeto de configuración que pueda leerse, pero no modificarse directamente. Con closures podemos crear un módulo que exponga métodos de lectura y, opcionalmente, de actualización controlada.
const Config = (function() {
const datos = {
apiUrl: 'https://api.ejemplo.com',
timeout: 5000,
modoDebug: false
};
return {
get(key) {
return datos[key];
},
set(key, value) {
if (key in datos) {
console.warn('Sobrescribir configuración no recomendada');
}
datos[key] = value;
},
todas() {
// devolvemos una copia para evitar mutaciones externas
return { ...datos };
}
};
})();
console.log(Config.get('apiUrl'));
Config.set('modoDebug', true);
console.log(Config.todas());
El objeto datos está encerrado dentro del IIFE (Immediately Invoked Function Expression). Nadie fuera del módulo puede tocarlo directamente, lo que protege la configuración de cambios inesperados.
Conclusión
Los closures no son sólo una curiosidad académica; son una herramienta práctica para encapsular estado, evitar la contaminación del scope global y crear APIs limpias. En los ejemplos anteriores hemos visto cómo pueden simplificar temporizadores, lazy loading y gestión de configuración. La próxima vez que te encuentres repitiendo variables en varios lugares, pregúntate: "¿Podría este estado vivir dentro de un closure?". Probablemente la respuesta sea sí, y tu código ganará en claridad y seguridad.