Closures en JavaScript: ¿Qué son? Con ejemplos!
Los closures en JavaScript son una poderosa característica que permite a una función recordar el scope donde fue creada, incluso después de haber finalizado su ejecución. Gracias al ámbito léxico, una función puede acceder a variables externas, creando un código más flexible y eficiente. Descubre qué es, cómo funciona y mira un ejemplo práctico que lo explica en acción.

Índice
1. ¿Qué es un closure en JavaScript?
En JavaScript, un closure es la combinación de una función y el ámbito léxico en el que se declaró esa función. En otras palabras, un closure permite que una función interna acceda a las variables de su función externa, incluso después de que la función externa haya finalizado su ejecución.
¿CÓMO FUNCIONA?
Cuando se declara una función dentro de otra función (función anidada), la función interna tiene acceso al ámbito de la función externa. Este ámbito incluye las variables y parámetros de la función externa.
Cuando la función externa finaliza, normalmente su ámbito se destruiría. Sin embargo, si la función interna todavía tiene acceso a ese ámbito (debido a un closure), el ámbito se mantiene en memoria.
Esto es posible porque en JavaScript, las funciones no solo encapsulan código, sino también su contexto de ejecución.
- Ejemplo para entender como funciona el closure:
- Resumen del funcionamiento de los closures paso a paso:
Conceptos a tener en cuenta para entender los closures
VARIABLES → ¿Qué son las variables en JavaScript?
- Son como cajas donde guardamos información (números, texto, etc.).
- En JavaScript, podemos declararlas con
let,constovar. letyconsttienen un comportamiento más predecible en cuanto a su alcance.
FUNCIONES → ¿Qué son las funciones en JavaScript?
- Son bloques de código que realizan una tarea específica.
- Las usamos para organizar nuestro código y reutilizarlo.
- Pueden recibir datos (parámetros) y devolver resultados.
FUNCIONES ANIDADAS:
- Son funciones definidas dentro de otras funciones.
- La función interna tiene acceso a las variables de la función externa.
VALOR DE RETORNO DE FUNCIONES: ¿Cómo usar el valor de retorno?
- Es el resultado que una función entrega después de completar su tarea.
- Usamos la palabra clave
returndentro de la función para especificar qué valor queremos devolver. - Cuando JavaScript encuentra
return, la función se detiene y entrega ese valor. - Permite que las funciones compartan resultados con otras partes de tu código.
PARÁMETROS → ¿Para que sirven los parámetros en JavaScript?
- Son los datos que le pasamos a una función cuando la llamamos.
- Funcionan como variables locales dentro de la función.
ÁMBITO → ¿Qué es el ámbito (scope) de una función?
- Es el área donde una variable es accesible.
- Puede ser global (accesible desde cualquier parte) o local (accesible solo dentro de una función).
ÁMBITO LÉXICO → ¿Qué es el ámbito (scope) léxico en JS?
- Determina la accesibilidad de las variables basándose en la estructura del código.
- Las funciones internas acceden a las variables de las funciones externas donde fueron definidas.
HOISTING → ¿Qué es el hoisting en JavaScript?
- Es un comportamiento de JavaScript donde las declaraciones de variables y funciones se «elevan» al principio de su ámbito antes de la ejecución del código.
- Es importante saber que solo la declaración es elevada, no la inicialización.
1.1. Ejemplo para entender como funciona el closure:
function crearContador() {
let contador = 0; // Variable en el scope de la función externa
return function incrementar() {
contador++; // La función interna accede a la variable externa
console.log(contador);
};
}
const miContador = crearContador(); // `crearContador()` ejecuta y retorna `incrementar()`
miContador(); // 1
miContador(); // 2
miContador(); // 3Lenguaje del código: JavaScript (javascript)
🔍Analicemos el código paso a paso, enfocándonos en cómo el closure hace su magia:
function crearContador():- Esta es la función externa.
- Dentro de ella, se declara
let contador = 0;. Esta variable está en el ámbito decrearContador. - Luego, se define una función interna,
incrementar.
return function incrementar():- Dentro de
crearContador, estamos devolviendo una nueva función interna llamadaincrementar. - Aquí es donde el closure entra en juego.
incrementar«recuerda» el ámbito en el que fue creada, es decir, el ámbito decrearContador, incluyendo la variablecontador.
- Dentro de
const miContador = crearContador();:- Cuando llamamos a
crearContador(), se ejecuta la función. - Se crea la variable
contadory se inicializa en 0. - Se devuelve la función
incrementar. - La constante
miContadorahora es referencia a esa funciónincrementar, pero lo importante es que la variablecontadorsigue existiendo en el contexto de esa función. - Es decir,
miContador«recuerda» ese estado inicial decontador(que es 0).
- Cuando llamamos a
miContador(); // 1- Aquí llamamos a la función
incrementar(que ahora está almacenada enmiContador). incrementaraccede a la variablecontadordel ámbito decrearContador(gracias al closure).- Incrementa
contadora 1 y muestra 1 en la consola.
- Aquí llamamos a la función
miContador(); // 2- Volvemos a llamar a
incrementar. - Como
incrementar«recuerda» el mismo ámbito, sigue accediendo a la misma variablecontador. - Incrementa
contadora 2 y muestra 2.
- Volvemos a llamar a
miContador(); // 3- Lo mismo sucede.
contadorse incrementa a 3, y se muestra 3.
- Lo mismo sucede.
PUNTOS CLAVE:
- Persistencia del ámbito:
- Aunque
crearContadorya terminó de ejecutarse, su ámbito (y la variablecontador) se mantiene en memoria porqueincrementarlo necesita.
- Aunque
- Encapsulamiento:
- La variable
contadorestá «oculta» dentro del closure. Solo se puede acceder a ella a través de la funciónincrementar.
- La variable
- Reutilización:
- Cada vez que se llama la función mi contador, se esta llamando la función incrementar, y esta función tiene guardada en memoria el valor de la variable contador, por eso se puede incrementar.
🔥EN RESUMEN: El closure permite que incrementar conserve el acceso a contador incluso después de que crearContador haya finalizado, creando un contador persistente.
1.2. Resumen del funcionamiento de los closures paso a paso:
1️⃣ Una función externa define una variable en su ámbito (scope).
2️⃣ Dentro de esa función, se define y devuelve otra función interna.
3️⃣ La función interna sigue «recordando» el scope donde fue creada, incluso cuando se ejecuta fuera del contexto original.
4️⃣ Esto permite que la función interna acceda y manipule variables de la función externa de forma persistente.
2. ¿Para que son útiles los clousers en JavaScript?
Los closures son una herramienta poderosa en JavaScript y tienen varios casos de uso importantes. Aquí te presento algunos ejemplos de sus ventajas con explicaciones:
- Encapsulamiento de datos y variables privadas:
- Funciones de fábrica:
- Callbacks y eventos asíncronos:
- Módulos:
2.1. Encapsulamiento de datos y variables privadas:
Los closures permiten crear variables privadas a las que solo se puede acceder desde dentro de una función específica. Esto ayuda a proteger los datos y evitar modificaciones accidentales.
function crearContador(valorInicial) {
let contador = valorInicial || 0; // Variable privada
return {
incrementar: function() {
contador++;
return contador;
},
decrementar: function() {
contador--;
return contador;
},
obtenerValor: function() {
return contador;
}
};
}
const miContador = crearContador(10);
console.log(miContador.incrementar()); // 11
console.log(miContador.decrementar()); // 10
console.log(miContador.obtenerValor()); // 10
// contador no es accesible directamente desde fuera de la función
Lenguaje del código: JavaScript (javascript)
Explicación:
crearContadorcrea una variablecontadorque está encerrada dentro de su ámbito.- Devuelve un objeto con tres funciones que actúan como «métodos» para interactuar con
contador. - Como
contadorestá dentro del closure, no se puede acceder a él directamente desde fuera decrearContador.
2.2. Funciones de fábrica:
Los closures se pueden utilizar para crear funciones que generan otras funciones con comportamientos personalizados.
function crearSumador(x) {
return function(y) {
return x + y;
};
}
const sumar5 = crearSumador(5);
const sumar10 = crearSumador(10);
console.log(sumar5(3)); // 8
console.log(sumar10(7)); // 17
Lenguaje del código: JavaScript (javascript)
Explicación:
crearSumadortoma un argumentoxy devuelve una función interna que sumaxa otro argumentoy.- Cada vez que llamamos a
crearSumador, creamos una nueva función que «recuerda» el valor dexgracias al closure.
2.3. Callbacks y eventos asíncronos:
Los closures son muy útiles para trabajar con callbacks, especialmente en operaciones asíncronas.
function ejecutarDespuesDe(segundos, callback) {
setTimeout(callback, segundos * 1000);
}
function mostrarMensaje(mensaje) {
console.log(mensaje);
}
ejecutarDespuesDe(2, function() {
mostrarMensaje("Este mensaje se muestra después de 2 segundos.");
});
Lenguaje del código: JavaScript (javascript)
Explicación:
ejecutarDespuesDetoma un número de segundos y una función de callback.setTimeoutllama al callback después del tiempo especificado.- El callback tiene acceso a las variables del ámbito donde fue creado, incluso después de que
ejecutarDespuesDehaya finalizado.
2.4. Módulos:
Los closures se utilizan para crear módulos en JavaScript, permitiendo encapsular código y exponer solo las partes necesarias.
const moduloContador = (function() {
let contador = 0;
function incrementar() {
contador++;
}
function obtenerValor() {
return contador;
}
return {
incrementar: incrementar,
obtenerValor: obtenerValor
};
})();
moduloContador.incrementar();
console.log(moduloContador.obtenerValor()); // 1
Lenguaje del código: JavaScript (javascript)
Explicación:
- Usamos una función autoejecutable (IIFE) para crear un ámbito privado.
- Dentro del IIFE, definimos variables y funciones privadas.
- Devolvemos un objeto que expone solo las funciones que queremos hacer públicas.
3. ¿Cómo se crean los closures? Paso a paso:
- PASO 1: Definir una función externa con variables internas
- PASO 2: Definir una función interna dentro de la externa
- PASO 3: Hacer que la función externa retorne la función interna
- PASO 4: Guardar el resultado de la función externa en una variable
- PASO 5: Llamar a la función almacenada para acceder a las variables internas
PASO 1: Definir una función externa con variables internas
La función externa es donde se definirán las variables que queremos encapsular dentro del closure.
Ejemplo:
function contador() {
let numero = 0; // Variable interna, no accesible desde afuera
}Lenguaje del código: JavaScript (javascript)
🔹 Aquí tenemos una función contador() que declara una variable numero.
🔹 Esta variable está dentro del ámbito local de contador(), por lo que no puede ser accedida desde fuera.
PASO 2: Definir una función interna dentro de la externa
Aquí creamos una función interna dentro de contador() que usará la variable numero.
Ejemplo:
function contador() {
let numero = 0; // Variable interna
function incrementar() {
numero++;
console.log(numero);
}
}Lenguaje del código: JavaScript (javascript)
🔹 Ahora, incrementar() está dentro de contador() y puede acceder a numero porque se encuentra en el mismo ámbito léxico.
🔹 Pero si llamamos a contador(), no pasará nada, porque incrementar() no se ejecuta automáticamente.
PASO 3: Hacer que la función externa retorne la función interna
Para crear el closure, la función externa debe devolver la función interna.
Ejemplo:
function contador() {
let numero = 0; // Variable interna
function incrementar() {
numero++;
console.log(numero);
}
return incrementar; // Retornamos la función interna
}Lenguaje del código: JavaScript (javascript)
🔹 Ahora, cuando contador() sea ejecutado, retornará la función incrementar, pero sin ejecutarla todavía.
🔹 La variable numero seguirá existiendo en memoria, incluso después de que contador() termine su ejecución.
PASO 4: Guardar el resultado de la función externa en una variable
Como contador() devuelve la función incrementar(), podemos almacenar ese resultado en una variable.
Ejemplo:
const miContador = contador(); // Guardamos el closureLenguaje del código: JavaScript (javascript)
🔹 miContador ahora contiene la función incrementar(), que sigue recordando la variable numero de contador().
PASO 5: Llamar a la función almacenada para acceder a las variables internas
Finalmente, al ejecutar miContador(), veremos que la variable numero se sigue incrementando, incluso si contador() ya terminó.
Ejemplo final:
function contador() {
let numero = 0; // Variable privada
function incrementar() {
numero++;
console.log(numero);
}
return incrementar; // Retornamos la función interna
}
const miContador = contador(); // Creamos un closure
miContador(); // 1
miContador(); // 2
miContador(); // 3Lenguaje del código: JavaScript (javascript)
🔹 miContador() sigue recordando la variable numero incluso después de que contador() haya finalizado su ejecución.
🔹 Cada vez que llamamos a miContador(), numero sigue existiendo y se incrementa correctamente.
🔹 Si creamos un nuevo contador, se creará una nueva variable independiente.
const otroContador = contador();
otroContador(); // 1
otroContador(); // 2
miContador(); // 4 (el anterior contador sigue con su propio estado)Lenguaje del código: JavaScript (javascript)
4. ¿Cuál es la diferencia entre closure y función normal?
La diferencia principal entre un closure y una función regular es:
- Un CLOSURE puede recordar y acceder a variables de su función exterior incluso después de que esta haya terminado de ejecutarse.
- Una FUNCIÓN REGULAR solo puede acceder a su propio scope y a los scopes superiores en el momento de la ejecución.
4.1. Ejemplo de diferencia entre función regular vs. closure
📌 Función regular (No mantiene estado)
function saludar(nombre) {
return 'Hola, ' + nombre + '!';
}
console.log(saludar('Juan')); // "Hola, Juan!"
console.log(saludar('María')); // "Hola, María!"Lenguaje del código: JavaScript (javascript)
Explicación:
saludar(nombre)es una función regular, toma un argumento (nombre) y devuelve un saludo.- No recuerda nada después de ejecutarse; cada llamada empieza desde cero.
📌 Closure (Mantiene estado)
function crearSaludo(saludo) {
return function(nombre) {
console.log(saludo + ', ' + nombre + '!');
};
}
const saludarEnEspañol = crearSaludo('Hola');
saludarEnEspañol('Juan'); // "Hola, Juan!"
saludarEnEspañol('María'); // "Hola, María!"
const saludarEnInglés = crearSaludo('Hello');
saludarEnInglés('John'); // "Hello, John!"Lenguaje del código: JavaScript (javascript)
Explicación:
crearSaludo(saludo)retorna una función interna que recuerda el valor desaludo(closure).- Aunque
crearSaludo()haya terminado su ejecución, la función interna aún tiene acceso a la variablesaludo, porque la «recuerda». - Cada función generada mantiene su propio estado (
Holaen español,Helloen inglés).
4.2. Diferencias clave:
| Característica | Función Regular | Closure |
|---|---|---|
| Acceso a Variables | Solo accede a variables en su scope inmediato. | Recuerda variables de su función exterior. |
| Mantenimiento de Estado | No mantiene estado entre ejecuciones. | Puede mantener datos privados entre ejecuciones. |
| Seguridad | Variables pueden ser accesibles y modificadas desde fuera. | Permite encapsular datos, evitando accesos externos. |
| Uso Común | Operaciones simples sin necesidad de mantener información. | Contadores, manejadores de eventos, programación funcional, optimización. |
4.3. Conclusión:
✅ Las funciones regulares son ideales para cálculos y procesos que no requieren almacenar información después de su ejecución.
✅ Los closures son perfectos cuando necesitas recordar valores previos, encapsular datos privados o crear funciones más flexibles y reutilizables.
5. Ejemplos de uso real de clusures en la práctica:
- 1️⃣ Manejo de eventos en el DOM
- Cuando haces clic en un botón, se ejecuta una función que responde al evento.
- 2️⃣ Operaciones asíncronas con setTimeout y setInterval
- Ejecutar código después de un tiempo determinado o de manera repetida.
- 3️⃣ Peticiones HTTP (AJAX, Fetch, Axios)
- Hacer solicitudes a una API y ejecutar código cuando la respuesta llegue.
- 4️⃣ Manejo de archivos en Node.js
- Leer o escribir archivos y ejecutar una función cuando la operación finaliza.
- 5️⃣ Funciones de orden superior (map, filter, reduce)
- Transformar, filtrar o reducir arrays de datos usando callbacks.
- 6️⃣ Middleware en Express.js
- Ejecutar funciones en una secuencia para manejar solicitudes en un servidor web.
- 7️⃣ Autenticación de usuarios
- Verificar credenciales antes de permitir el acceso a un recurso.
- 8️⃣ Animaciones en JavaScript
- Ejecutar código en cada cuadro de animación (
requestAnimationFrame). - 9️⃣ Validación de formularios
- Ejecutar funciones de validación antes de enviar datos a un servidor.
- 🔟 Creación de APIs y WebSockets
- Ejecutar funciones en tiempo real cuando llegan datos de otros usuarios.
6. ¿Qué problemas comunes pueden surgir al usar closures?
Los closures son una herramienta poderosa, pero hay que usarlos con cuidado para evitar problemas de rendimiento, consumo de memoria y efectos no deseados en asincronía.
Veamos estos puntos en profundidad:
- Closures y rendimiento → ¿Cuándo pueden ser costosos en memoria?
- ¿Cómo evitar memory leaks en closures mal gestionados?
- Closures y el Event Loop → ¿Cómo se comportan en asincronía?
6.1. Closures y rendimiento → ¿Cuándo pueden ser costosos en memoria?
Los closures mantienen en memoria todas las variables del ámbito donde fueron creados, incluso si ya no se usan. Esto puede ser problemático si almacenamos demasiada información o si los closures se quedan referenciados innecesariamente.
6.1.1. Ejemplo de un closure que ocupa memoria innecesariamente:
function crearManejadorDeUsuarios() {
let usuarios = []; // Se mantiene en memoria
return function agregarUsuario(nombre) {
usuarios.push(nombre);
console.log(usuarios);
};
}
const manejador = crearManejadorDeUsuarios();
manejador("Manuel"); // ["Manuel"]
manejador("Alejandro"); // ["Manuel", "Alejandro"]Lenguaje del código: JavaScript (javascript)
⚠️ ¿EL PROBLEMA?: La variable usuarios queda en memoria mientras manejador siga existiendo, incluso si ya no la necesitamos. Si la lista crece mucho y el closure no se libera, se puede generar un consumo de memoria innecesario.
✅ SOLUCIÓN: Liberar la referencia a manejador cuando ya no se necesite.
manejador = null; // Ahora la memoria puede liberarseLenguaje del código: JavaScript (javascript)
6.2. ¿Cómo evitar memory leaks en closures mal gestionados?
Un memory leak ocurre cuando un objeto se mantiene en memoria aunque ya no sea necesario. En closures, esto puede suceder si hay referencias que impiden la recolección de basura (garbage collection).
6.2.1. Ejemplo de un closure que causa un memory leak:
function crearContador() {
let numero = 0;
return function incrementar() {
numero++;
console.log(numero);
};
}
const contador1 = crearContador();
const contador2 = crearContador();Lenguaje del código: JavaScript (javascript)
⚠️ Aquí contador1 y contador2 tienen cada uno su propio closure, lo que significa que cada uno mantiene su propia variable numero en memoria.
¿CÓMO EVITARLO?
1️⃣ Liberar referencias cuando ya no sean necesarias
contador1 = null; // Ahora `numero` de contador1 puede ser liberadoLenguaje del código: JavaScript (javascript)
2️⃣ No almacenar datos innecesarios dentro del closure
3️⃣ Usar estructuras más ligeras si solo se necesita una variable temporal (como WeakMap en ciertos casos).
6.3. Closures y el Event Loop → ¿Cómo se comportan en asincronía?
Los closures pueden capturar variables en memoria mientras esperan ser ejecutados en el futuro, como cuando se usan en setTimeout o Promesas.
6.3.1. Ejemplo de closure en asincronía con setTimeout:
function crearTemporizador() {
let mensaje = "¡Hola después de 3 segundos!";
setTimeout(function () {
console.log(mensaje); // El closure mantiene "mensaje" en memoria
}, 3000);
}
crearTemporizador();Lenguaje del código: JavaScript (javascript)
🔍 ¿Qué pasa aquí?
- Se ejecuta
crearTemporizador(), creando la variablemensaje. setTimeoutprograma una función que se ejecutará en 3 segundos.- Aunque
crearTemporizador()haya terminado, el closure sigue recordandomensajehasta que se ejecute la función asíncrona.
✅ PRECAUCIÓN: Si el closure mantiene referencias a datos grandes y el timeout es muy largo, puede consumir memoria innecesariamente.
7. Preguntas frecuentes y resumen sobre closures en JS.
Un closure es una función que recuerda el ámbito en el que fue creada, incluso después de que ese ámbito haya terminado su ejecución. Permite acceder a variables de su contexto externo, lo que facilita la encapsulación y la gestión de datos privados.
Un closure se crea cuando una función interna accede a variables de una función externa, incluso después de que esta haya finalizado. Gracias al scope léxico, la función mantiene una referencia a su entorno, permitiendo persistencia de datos.
Se usan para encapsular datos, evitar la contaminación del ámbito global, crear funciones con estado privado y definir callbacks o funciones de orden superior en estructuras como setTimeout y map.
Una función regular solo accede a variables definidas en su propio ámbito o en el global. Un closure, en cambio, mantiene referencias a variables de su contexto externo, incluso después de que la función externa haya finalizado.
Los closures permiten que las variables definidas en una función externa sigan existiendo en memoria mientras la función interna que las usa siga siendo accesible, evitando así su recolección de basura.
Pueden generar problemas de memoria si las variables referenciadas no se liberan correctamente. Además, en bucles pueden producir resultados inesperados si no se usa let en lugar de var para mantener el estado correcto.
Para evitar problemas, usa let en lugar de var, o encapsula la lógica dentro de una función autoejecutable para capturar correctamente el valor de cada iteración.
El scope léxico significa que una función accede a variables según el lugar donde fue definida, no donde se ejecuta. Los closures aprovechan esto para recordar el contexto en el que fueron creados.
Los closures permiten ocultar variables dentro de una función, haciendo que solo sean accesibles mediante métodos específicos. Ejemplo: const contador = (() => { let num = 0; return () => num++; })();.
Algunos ejemplos incluyen contadores privados, gestión de eventos, manejo de configuraciones en módulos y creación de funciones personalizadas con parámetros predefinidos mediante currying.
