Closures y Scope en JavaScript Explicados Simple

Frontend2026-09-19TryQuickToolBox

Estás escribiendo una función de JavaScript que devuelve otra función. La función interna usa una variable de la función externa, pero cuando la llamas más tarde, la función externa ya ha terminado. Sin embargo, la variable sigue ahí. ¿Cómo? Esa es la magia de los closures y el scope. Muchos desarrolladores luchan con este concepto, lo que lleva a errores como valores de variables inesperados o fugas de memoria. En este artículo, desglosaremos los closures y el scope de JavaScript en términos simples con ejemplos prácticos.

¿Qué es el Scope en JavaScript?

El scope determina dónde son accesibles las variables en tu código. JavaScript tiene tres tipos principales de scope:

Aquí hay un ejemplo rápido:

let globalVar = 'I am global';

function myFunc() {
  let functionVar = 'I am in function scope';
  if (true) {
    let blockVar = 'I am in block scope';
    console.log(globalVar); // accessible
    console.log(functionVar); // accessible
    console.log(blockVar); // accessible
  }
  console.log(blockVar); // ReferenceError: blockVar is not defined
}

myFunc();
console.log(functionVar); // ReferenceError: functionVar is not defined

Entender el scope es crucial porque previene colisiones de variables y ayuda a gestionar la memoria.

Alcance Léxico: La Base de los Closures

JavaScript utiliza alcance léxico, lo que significa que el scope de una variable está determinado por su posición en el código fuente. Las funciones internas tienen acceso a las variables en su scope externo (envolvente). Esto también se llama alcance estático.

Considera esto:

function outer() {
  let outerVar = 'outer';
  function inner() {
    console.log(outerVar); // 'outer'
  }
  inner();
}
outer();

La función inner puede acceder a outerVar porque está léxicamente dentro de outer. Esto no es dinámico; no depende de cómo se llame a la función.

¿Qué es un Closure?

Un closure es una función que recuerda su scope léxico incluso cuando la función se ejecuta fuera de ese scope. En otras palabras, un closure te da acceso al scope de una función externa desde una función interna. Los closures se crean cada vez que se crea una función, en el momento de la creación de la función.

Ejemplo:

function makeCounter() {
  let count = 0;
  return function() {
    count++;
    return count;
  };
}

const counter = makeCounter();
console.log(counter()); // 1
console.log(counter()); // 2

Aquí, makeCounter devuelve una función interna que incrementa count. Incluso después de que makeCounter haya terminado de ejecutarse, la función devuelta todavía tiene acceso a count. Eso es un closure.

Cómo Funcionan los Closures Internamente

Cuando se crea una función, almacena una referencia a su entorno léxico (la cadena de scope). Cuando se invoca la función, utiliza ese entorno para resolver variables. El entorno se mantiene vivo mientras exista el closure.

Usos Prácticos de los Closures

Los closures no son solo teóricos; se utilizan en todas partes en JavaScript.

1. Privacidad de Datos (Patrón Módulo)

Antes de los módulos ES6, los closures se usaban para crear variables privadas:

const counterModule = (function() {
  let count = 0;
  return {
    increment: function() { count++; },
    getCount: function() { return count; }
  };
})();

counterModule.increment();
console.log(counterModule.getCount()); // 1
console.log(counterModule.count); // undefined

Aquí, count es privado y solo accesible a través de los métodos devueltos.

2. Manejadores de Eventos y Callbacks

Los closures se utilizan comúnmente en manejadores de eventos para acceder a variables del scope externo:

function setupButton() {
  let clickCount = 0;
  document.getElementById('myButton').addEventListener('click', function() {
    clickCount++;
    console.log(`Button clicked ${clickCount} times`);
  });
}

El closure del manejador de eventos recuerda clickCount.

3. Fábricas de Funciones

Puedes crear funciones con argumentos preestablecidos:

function multiplyBy(factor) {
  return function(number) {
    return number * factor;
  };
}

const double = multiplyBy(2);
console.log(double(5)); // 10

Errores Comunes y Cómo Evitarlos

Los closures pueden llevar a un comportamiento inesperado si no se entienden.

Captura de Variables en Bucles

Usar var en bucles crea un único enlace compartido:

for (var i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i); // prints 3, 3, 3
  }, 100);
}

Todos los callbacks comparten el mismo i. Para solucionarlo, usa let (scope de bloque) o un IIFE:

for (let i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i); // prints 0, 1, 2
  }, 100);
}

Fugas de Memoria

Los closures mantienen las variables vivas. Si un closure está adjunto a un objeto de larga vida (como un elemento DOM), puede evitar la recolección de basura. Siempre elimina los listeners de eventos cuando ya no sean necesarios.

Cadena de Scope y Resolución de Variables

Cuando accedes a una variable, JavaScript busca en la cadena de scope: primero el scope actual, luego los scopes externos, hasta el scope global. Si no se encuentra, se lanza un ReferenceError.

Ejemplo:

let a = 'global';
function outer() {
  let b = 'outer';
  function inner() {
    let c = 'inner';
    console.log(a, b, c); // all accessible
  }
  inner();
}
outer();

La cadena de scope para inner es: inner -> outer -> global.

Comparación: var, let y const

Palabra claveScopeHoistingReasignable
varFunciónSí (inicializada a undefined)Sí
letBloqueSí (pero no inicializada, TDZ)Sí
constBloqueSí (pero no inicializada, TDZ)No

Entender estas diferencias es clave para evitar errores relacionados con el scope.

Preguntas Frecuentes

¿Qué es un closure en JavaScript?

Un closure es una función que mantiene el acceso a su scope léxico incluso cuando se ejecuta fuera de ese scope. Permite que una función "recuerde" variables de donde fue definida.

¿Por qué son importantes los closures?

Los closures permiten la privacidad de datos, fábricas de funciones y manejadores de eventos que mantienen estado. Son fundamentales para muchos patrones de JavaScript.

¿Cómo puedo evitar errores relacionados con closures?

Usa let y const para el scope de bloque, ten cuidado con los bucles y limpia los listeners de eventos para prevenir fugas de memoria.

¿Listo para probar tu comprensión? Prueba nuestro JSON Formatter para experimentar con fragmentos de código y ver cómo funcionan los closures en tiempo real.