Closures vs variables globales: cuándo usar cada una
Los closures no son magia. Te muestro cuándo realmente los necesitás y cuándo es mejor usar variables globales en JavaScript.
NUCBA
Closures vs variables globales: cuándo usar cada una
Los closures en JavaScript tienen fama de complicados. La realidad es que los usás todo el tiempo sin darte cuenta. El problema es que la mayoría de los tutoriales te explican con ejemplos de contadores que nunca vas a escribir en la vida real.
Te voy a mostrar cuándo los closures realmente te sirven, cuándo es mejor usar variables globales, y cómo decidir entre una y otra opción.
Qué son los closures (sin verso)
Un closure es cuando una función "recuerda" las variables del scope donde fue creada, incluso después de que ese scope ya no existe.
function crearContador() { let count = 0; return function() { count++; return count; }; } const contador = crearContador(); console.log(contador()); // 1 console.log(contador()); // 2
La función interna todavía puede acceder a count aunque crearContador ya terminó de ejecutarse. Eso es un closure.
Casos reales donde los closures te salvan
Encapsular estado en componentes
Supongamos que tenés un modal que necesita controlar su propio estado:
function crearModal(selector) { const elemento = document.querySelector(selector); let estaAbierto = false; return { abrir() { if (!estaAbierto) { elemento.style.display = 'block'; estaAbierto = true; } }, cerrar() { if (estaAbierto) { elemento.style.display = 'none'; estaAbierto = false; } }, toggle() { estaAbierto ? this.cerrar() : this.abrir(); } }; } const modalUsuario = crearModal('#modal-usuario'); const modalConfirmacion = crearModal('#modal-confirmacion');
Cada modal tiene su propio estado estaAbierto que no puede ser modificado desde afuera. No hay forma de romperlo accidentalmente.
Configurar event listeners con datos específicos
Cuando tenés una lista de elementos y cada uno necesita comportarse diferente:
function configurarBotones(productos) { productos.forEach(producto => { const boton = document.querySelector(`#btn-${producto.id}`); boton.addEventListener('click', function() { // El closure "captura" el producto específico agregarAlCarrito(producto.id, producto.precio); mostrarNotificacion(`${producto.nombre} agregado al carrito`); }); }); }
Sin closures tendrías que usar data- attributes o alguna otra solución más verbosa.
Crear funciones de configuración
Este patrón es muy común en librerías:
function crearValidador(reglas) { return function(valor) { for (let regla of reglas) { if (!regla.test(valor)) { return { valido: false, error: regla.mensaje }; } } return { valido: true }; }; } const validarEmail = crearValidador([ { test: v => v.includes('@'), mensaje: 'Debe contener @' }, { test: v => v.length > 5, mensaje: 'Muy corto' } ]); const validarPassword = crearValidador([ { test: v => v.length >= 8, mensaje: 'Mínimo 8 caracteres' }, { test: v => /\d/.test(v), mensaje: 'Debe tener números' } ]);
Cuándo usar variables globales en su lugar
Los closures no siempre son la respuesta. A veces una variable global es más simple y clara.
Estado de aplicación compartido
Si múltiples partes de tu app necesitan acceder al mismo dato:
// Mejor así window.usuarioActual = null; window.configuracion = { tema: 'claro', idioma: 'es' }; // Que crear closures complicados para compartir estado function crearEstadoGlobal() { let usuario = null; let config = { tema: 'claro', idioma: 'es' }; return { getUsuario: () => usuario, setUsuario: (u) => usuario = u, // ... más getters y setters }; }
Configuración que no cambia
Para constantes que usás en toda la aplicación:
// Directo y claro window.API_BASE = 'https://api.miapp.com'; window.COLORES = { primario: '#007bff', secundario: '#6c757d', exito: '#28a745' };
Debugging más fácil
Las variables globales son más fáciles de inspeccionar desde DevTools:
// Podés hacer console.log(window.estadoDebug) en cualquier momento window.estadoDebug = { ultimaAccion: null, errores: [], tiempos: {} };
Cómo decidir entre closures y variables globales
Usá closures cuando:
- Necesitás múltiples "instancias" del mismo comportamiento
- Querés encapsular estado privado
- Estás creando una librería o componente reutilizable
- El estado es específico de una funcionalidad
Usá variables globales cuando:
- El dato se comparte entre muchas partes de la app
- Es configuración que no cambia frecuentemente
- Necesitás acceso fácil para debugging
- La simplicidad es más importante que la encapsulación
Errores comunes con closures
El clásico problema del loop
// MAL - todas las funciones referencian la misma variable i for (var i = 0; i < 3; i++) { setTimeout(function() { console.log(i); // Imprime 3, 3, 3 }, 100); } // BIEN - cada función tiene su propia copia de i for (let i = 0; i < 3; i++) { setTimeout(function() { console.log(i); // Imprime 0, 1, 2 }, 100); } // O usando closures explícitamente for (var i = 0; i < 3; i++) { (function(j) { setTimeout(function() { console.log(j); // Imprime 0, 1, 2 }, 100); })(i); }
Memory leaks por referencias circulares
// Cuidado con esto function crearHandler(elemento) { return function() { // El closure mantiene una referencia al elemento // Y el elemento mantiene una referencia al handler elemento.innerHTML = 'Clickeado'; }; } // Mejor function crearHandler(elemento) { const id = elemento.id; return function() { document.getElementById(id).innerHTML = 'Clickeado'; }; }
Preguntas frecuentes
¿Los closures afectan la performance? Mínimamente. El overhead es negligible en aplicaciones normales. Solo preocupate si estás creando miles de closures por segundo.
¿Puedo "romper" un closure desde afuera? No directamente. Las variables capturadas son privadas. Pero podés exponer métodos para modificar el estado interno si necesitás.
¿Es mejor usar modules que closures? Para organizar código grande, sí. Los modules son más claros y tienen mejor tooling. Usá closures para encapsular comportamiento específico dentro de modules.