En JavaScript, un closure (o clausura) es una funcionalidad que permite a las funciones tener acceso al ámbito de la función que las contiene, incluso después de que esta función externa haya terminado de ejecutarse.
En otras palabras, un closure “cierra” el ámbito de la función que lo creó, lo que permite que las variables locales de esa función permanezcan accesibles.
Las closures son una funcionalidad automática. JavaScript gestiona las closures por nosotros cuando es necesario (es decir, que no tenemos que hacer nada).
Sin embargo, es interesante conocer su funcionamiento tanto para evitar errores inesperados, como porque tiene repercusiones en la gestión de memoria de JavaScript.
Crean las closures
Como decía, las closures se crean de forma automática. Esto ocurre cuando una función interna se define dentro de otra función.
La closue permite que la función interna tenga acceso a las variables de la función externa, incluso después de que la función externa haya finalizado su ejecución.
Vamos a verlo mejor con un ejemplo:
function crearContador() {
let cuenta = 0;
return function() {
cuenta++;
return cuenta;
};
}
const contador = crearContador();
console.log(contador()); // 1
console.log(contador()); // 2
console.log(contador()); // 3
En este ejemplo:
crearContador
es una función que define una variable localcuenta
y devuelve una función anónima- La función interna tiene acceso a la variable
cuenta
de su función externa (crearContador
) - La función externa
crearContador
ya ha terminado de ejecutarse. Por lo que podríamos pensar que deja de existir la variablecuenta
- Sin enbargo, el contador sigue funcionando 🤔
- Esto es así porque JavaScript ha creado una closure, de forma
cuenta
sigue existiendo
Cómo funcionan los closures
Para entender cómo funcionan los closures, es importante entender cómo JavaScript maneja el ámbito y la memoria.
Ámbito léxico
JavaScript utiliza el concepto de ámbito léxico (o lexical scope), lo que significa que el ámbito de una función se determina en el momento de su creación, no en el momento de su ejecución.
Cuando se crea un closure, la función interna “recuerda” el entorno léxico en el que fue creada, lo que incluye todas las variables y parámetros de la función externa.
Cadenas de ámbito
Cuando se accede a una variable en una función, JavaScript sigue una cadena de ámbito (o scope chain).
Primero, busca la variable en el ámbito local. Si no se encuentra, busca en el ámbito de la función externa, y así sucesivamente hasta llegar al ámbito global.
Los closures preservan esta cadena de ámbito, permitiendo el acceso a las variables de la función externa.
Precauciones al usar closures
Aunque los closures son útiles y tienen muchas ventajas, también pueden llevar a un consumo mayor de memoria (si no se manejan adecuadamente).
Si una closure mantiene referencias a variables que ya no son necesarias, puede causar que estas variables no sean recolectadas por el recolector de basura, lo que resulta en fugas de memoria.
Libera Recursos: Cuando las closures ya no son necesarias, asegúrate de que no mantengan referencias a recursos que puedan ser liberados.
Ejemplos prácticos
Funciones de Callback
Las closures son esenciales en el uso de funciones de callback y promesas, ya que permiten acceder al contexto donde fueron creadas.
function esperar(ms) {
let mensaje = 'Espera completada';
setTimeout(function() {
console.log(mensaje);
}, ms);
}
esperar(2000); // "Espera completada" se imprime después de 2 segundos
En este ejemplo,
- La función interna del
setTimeout
es un closure - Esta recuerda el valor de la variable
mensaje
incluso después de que la funciónesperar
haya terminado de ejecutarse.
Uso con promesas
function procesarDatos(data) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(`Datos procesados: ${data}`);
}, 1000);
});
}
const datos = "Información importante";
procesarDatos(datos).then(console.log); // Output: "Datos procesados: Información importante"
En este ejemplo, la función de callback dentro de setTimeout
tiene acceso a la variable data
, gracias a las closures.
Funciones parciales y currying
Las closures son útiles para implementar técnicas como el currying y las funciones parciales, donde puedes fijar ciertos argumentos de una función y crear nuevas funciones con los argumentos restantes.
function multiplicar(factor) {
return function(numero) {
return numero * factor;
};
}
const multiplicarPor2 = multiplicar(2);
console.log(multiplicarPor2(5)); // Output: 10
Aquí, multiplicarPor2
es una closure que recuerda el valor 2
como su contexto, permitiendo realizar multiplicaciones fácilmente.