Volver a JavaScript Intermedio

Closures y Scope

Entender cómo JavaScript gestiona la visibilidad y memoria de variables es clave para escribir código predecible y sin errores.

Tipos de Scope

// Scope global const APP_NAME = 'MiApp'; function exterior() { // Scope de función const varExterior = 'Soy de exterior'; function interior() { // Scope de función (interior) const varInterior = 'Soy de interior'; console.log(varExterior); // ✅ interior accede a exterior console.log(APP_NAME); // ✅ interior accede al global } // console.log(varInterior); // ❌ ReferenceError }
{ // Block scope — let y const son de bloque let blockLet = 'bloque'; var blockVar = 'función'; } // console.log(blockLet); // ❌ ReferenceError console.log(blockVar); // ✅ var se escapa del bloque

Closures

Un closure es una función que recuerda las variables de su scope exterior incluso después de que ese scope ha terminado de ejecutarse.

function crearContador(inicio = 0) { let cuenta = inicio; // Capturada por el closure return { incrementar() { cuenta++; }, decrementar() { cuenta--; }, obtener() { return cuenta; }, }; } const contador = crearContador(10); contador.incrementar(); contador.incrementar(); console.log(contador.obtener()); // 12 // `cuenta` es privada — no accesible desde fuera

Patrones Prácticos con Closures

// Memoización function memoizar(fn) { const caché = new Map(); return function (...args) { const clave = JSON.stringify(args); if (caché.has(clave)) return caché.get(clave); const resultado = fn.apply(this, args); caché.set(clave, resultado); return resultado; }; } const fnCostosa = memoizar((n) => { console.log('Calculando...'); return n * n; }); fnCostosa(5); // Imprime "Calculando...", retorna 25 fnCostosa(5); // Retorna 25 de inmediato (desde caché)
// Aplicación parcial function multiplicar(a, b) { return a * b; } function parcial(fn, ...argsPreset) { return function (...argsRestantes) { return fn(...argsPreset, ...argsRestantes); }; } const doble = parcial(multiplicar, 2); const triple = parcial(multiplicar, 3); doble(5); // 10 triple(5); // 15

El Error Clásico del Bucle

// ERROR: var es de scope de función — todos los callbacks comparten el mismo `i` for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 100); // Imprime: 3, 3, 3 } // SOLUCIÓN 1: Usar let (scope de bloque) for (let i = 0; i < 3; i++) { setTimeout(() => console.log(i), 100); // Imprime: 0, 1, 2 } // SOLUCIÓN 2: Usar IIFE para crear nuevo scope por iteración for (var i = 0; i < 3; i++) { ((j) => setTimeout(() => console.log(j), 100))(i); // Imprime: 0, 1, 2 }

Hoisting

// Las declaraciones de función son totalmente "hoisted" saludar(); // ✅ Funciona! function saludar() { console.log('Hola'); } // var es "declaration-hoisted" (pero no inicializado) console.log(x); // undefined (sin error) var x = 5; // let/const están en la "temporal dead zone" hasta su declaración // console.log(y); // ❌ ReferenceError let y = 10;