JavaScript
Tutorial JavaScript: Funciones cierre (closure)
JavaScript closures: definición y ejemplos. Aprende a usar closures en JavaScript con ejemplos prácticos y detallados.
Aprende JavaScript y certifícateFundamentos de closures: sintaxis, diferencia entre scope y closure
En JavaScript, un closure (cierre) es una función que recuerda el entorno léxico en el que fue creada, incluso después de que dicho entorno haya dejado de existir. Esto significa que una función con closure tiene acceso a las variables de su scope (ámbito) exterior, preservando su estado a través del tiempo.
Para comprender los closures, es esencial recordar primero el concepto de scope. El scope determina la accesibilidad de las variables y funciones en diferentes partes del código. En JavaScript, existen principalmente dos tipos de scope: global y local. Las variables declaradas fuera de cualquier función tienen un scope global y son accesibles desde cualquier parte del código. Por otro lado, las variables declaradas dentro de una función tienen un scope local y solo son accesibles dentro de esa función.
Un closure se genera cuando una función interna accede a variables de una función externa y las mantiene en memoria, incluso después de que la función externa haya terminado de ejecutarse. Esto es posible porque en JavaScript, las funciones son ciudadanos de primera clase, lo que permite que se traten como cualquier otra variable: pueden asignarse a variables, pasarse como argumentos o retornarse desde otras funciones.
Aquí tienes un ejemplo que ilustra este concepto:
function crearContador() {
let contador = 0;
function incrementar() {
contador += 1;
console.log(contador);
}
return incrementar;
}
const contador = crearContador();
contador(); // Imprime 1
contador(); // Imprime 2
contador(); // Imprime 3
En este ejemplo, la función crearContador
define una variable local contador
y una función interna incrementar
que incrementa y muestra el valor de contador
. Al retornar incrementar
, estamos creando un closure que mantiene una referencia a contador
. Aunque crearContador
ha finalizado su ejecución, la función incrementar
sigue teniendo acceso a contador
gracias al closure.
La diferencia entre scope y closure radica en que el scope es el contexto actual de ejecución, que define qué variables y funciones están disponibles en un punto dado del código. Un closure, por su parte, es una función que captura variables de su scope exterior y las mantiene accesibles, incluso después de que dicho scope haya terminado.
Los closures son fundamentales para crear funciones privadas y encapsular datos. Permiten que una función recuerde el estado de su entorno, lo que es especialmente útil en programación asincrónica y en la creación de módulos. Sin embargo, es importante usarlos con cuidado, ya que un mal manejo puede llevar a fugas de memoria si las variables capturadas nunca se liberan.
En resumen, mientras que el scope define el alcance de las variables en diferentes partes del código, los closures permiten que una función acceda y retenga variables de un scope externo, proporcionando una poderosa herramienta para la abstracción y el encapsulamiento en JavaScript.
Implementación de closures
La implementación de closures en JavaScript aprovecha la capacidad de las funciones para acceder al contexto en el que fueron creadas. Para crear un closure, es necesario definir una función dentro de otra función y permitir que la función interna acceda a las variables de la función externa.
Considera el siguiente ejemplo básico:
function greeting(name) {
return function() {
console.log(`Hello, ${name}`);
};
}
const greetJohn = greeting('John');
greetJohn(); // Imprime "Hello, John"
En este ejemplo, la función externa greeting
recibe un parámetro name
y retorna una función interna anónima. La función interna accede al parámetro name
de la función externa, creando así un closure. Cuando invocamos greetJohn()
, la función interna recuerda el valor de name
aunque la ejecución de greeting
haya finalizado.
Es importante destacar que los closures no solo pueden acceder a variables, sino también modificarlas. Veamos un ejemplo donde una variable es incrementada a través de un closure:
function counter() {
let count = 0;
return function() {
count++;
console.log(count);
};
}
const increment = counter();
increment(); // Imprime 1
increment(); // Imprime 2
increment(); // Imprime 3
Aquí, la variable count
es privada para la función counter
y solo puede ser modificada a través del closure retornado. Esto permite encapsular el estado y prevenir accesos directos desde el exterior.
Los closures también pueden aceptar parámetros, lo que los hace aún más versátiles:
function multiplier(factor) {
return function(number) {
return number * factor;
};
}
const double = multiplier(2);
console.log(double(5)); // Imprime 10
const triple = multiplier(3);
console.log(triple(5)); // Imprime 15
En este caso, la función multiplier
crea un closure que retiene el valor de factor
. Las funciones double
y triple
son closures que recuerdan el valor con el que fueron creadas y lo utilizan al ser llamadas.
Para una implementación más elaborada, podemos usar closures para crear métodos en objetos:
function createPerson(name) {
let age = 0;
return {
incrementAge: function() {
age++;
console.log(`${name} is ${age} years old`);
}
};
}
const person = createPerson('Anna');
person.incrementAge(); // Imprime "Anna is 1 years old"
person.incrementAge(); // Imprime "Anna is 2 years old"
En este ejemplo, la variable age
es privada y solo accesible a través del método incrementAge
. Esto es posible gracias al closure que mantiene la referencia a age
.
Es importante entender que los closures pueden tener implicaciones en el uso de memoria. Como las variables en el scope exterior permanecen en memoria mientras existan referencias a los closures, es posible que se produzcan fugas de memoria si no se manejan adecuadamente. Por ello, es recomendable liberar las referencias a closures cuando ya no sean necesarias.
Además, al trabajar con loops y closures, es común encontrarse con comportamientos inesperados debido a variables compartidas. Observa este ejemplo erróneo:
for (var i = 1; i <= 3; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
// Imprime 4 tres veces
Este código intenta imprimir los valores de i
en cada iteración, pero debido al alcance de var
, el closure captura la referencia a la misma variable i
, que al final del loop es 4. Para solucionar esto, podemos emplear let
o crear un closure adicional:
for (let i = 1; i <= 3; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
// Imprime 1, 2 y 3 con un segundo de retraso
Al usar let
, creamos una nueva variable i
en cada iteración, y el closure captura el valor correcto. De este modo, evitamos el problema de la variable compartida y el closure funciona como se espera.
Comprender la implementación de closures en JavaScript es fundamental para escribir código más modular y mantener la integridad de las variables en nuestros programas. Los closures nos permiten crear funciones con estado y controlar el acceso a variables, mejorando así la abstracción y el diseño de nuestras aplicaciones.
Patrones comunes con closures: Encapsulación y datos privados, Funciones de fábrica (factory functions)
Los closures son fundamentales en JavaScript para implementar patrones de diseño que permiten una mejor organización y seguridad del código. Entre estos patrones, destacan la encapsulación de datos y el uso de funciones de fábrica (factory functions).
Encapsulación y datos privados
La encapsulación es un principio de programación que permite ocultar detalles internos de una función o objeto, exponiendo solo una interfaz pública para interactuar con ellos. En JavaScript, los closures facilitan la creación de datos privados, permitiendo que variables internas sean inaccesibles desde el exterior, asegurando así la integridad de los datos.
Considere el siguiente ejemplo:
function createCounter() {
let count = 0;
return {
increment() {
count++;
console.log(`Count is ${count}`);
},
decrement() {
count--;
console.log(`Count is ${count}`);
}
};
}
const counter = createCounter();
counter.increment(); // Imprime "Count is 1"
counter.increment(); // Imprime "Count is 2"
counter.decrement(); // Imprime "Count is 1"
En este caso, la variable count
es privada y no es accesible desde fuera de la función createCounter
. Los métodos increment
y decrement
tienen acceso a count
gracias al closure, pero el código externo no puede modificar count
directamente. Así, se consigue proteger el estado interno y controlar cómo se modifica.
Intentar acceder a count
desde fuera resultará en un error:
console.log(counter.count); // Imprime "undefined"
Esta capacidad de mantener datos privados es esencial para crear módulos o componentes más seguros y confiables.
Otro ejemplo es crear un almacén de datos con métodos para manipular la información:
function createPerson(name) {
let _name = name;
return {
getName() {
return _name;
},
setName(newName) {
_name = newName;
}
};
}
const person = createPerson('Alice');
console.log(person.getName()); // Imprime "Alice"
person.setName('Bob');
console.log(person.getName()); // Imprime "Bob"
Aquí, la propiedad _name
es privada y solo puede ser accedida o modificada mediante los métodos getName
y setName
. Esto permite controlar cómo se interactúa con los datos internos, evitando modificaciones indebidas.
Funciones de fábrica (factory functions)
Las funciones de fábrica son funciones que crean y retornan objetos. Utilizan closures para encapsular datos y comportamientos, ofreciendo una alternativa a las clases y la herencia clásica. Este patrón es especialmente útil para crear múltiples instancias con comportamientos similares pero datos independientes.
Un ejemplo básico de una función de fábrica es:
function createUser(username) {
return {
getUsername() {
return username;
}
};
}
const user1 = createUser('user_one');
const user2 = createUser('user_two');
console.log(user1.getUsername()); // Imprime "user_one"
console.log(user2.getUsername()); // Imprime "user_two"
En este caso, createUser
es una función de fábrica que genera objetos con un método getUsername
. La variable username
queda encapsulada y es única para cada instancia creada.
Las funciones de fábrica pueden también incluir métodos compartidos y datos privados más complejos:
function createBankAccount(initialBalance) {
let balance = initialBalance;
return {
deposit(amount) {
balance += amount;
console.log(`Deposited ${amount}. New balance is ${balance}.`);
},
withdraw(amount) {
if (amount > balance) {
console.log('Insufficient funds.');
} else {
balance -= amount;
console.log(`Withdrew ${amount}. New balance is ${balance}.`);
}
},
getBalance() {
return balance;
}
};
}
const account = createBankAccount(1000);
account.deposit(500); // Imprime "Deposited 500. New balance is 1500."
account.withdraw(2000); // Imprime "Insufficient funds."
account.withdraw(300); // Imprime "Withdrew 300. New balance is 1200."
console.log(account.getBalance()); // Imprime 1200
Aquí, el balance de la cuenta es un dato privado y solo puede ser modificado a través de los métodos proporcionados. Esto asegura que el estado de la cuenta se mantenga consistente y controlado.
Ventajas de las funciones de fábrica
Al utilizar funciones de fábrica, se obtienen varias ventajas:
Encapsulación: Los datos internos están protegidos y solo accesibles mediante la interfaz pública.
Flexibilidad: Es fácil crear objetos personalizados sin la complejidad de las clases y la herencia.
Composición sobre herencia: Permite combinar funcionalidades reutilizables en lugar de depender de cadenas de herencia.
Simplicidad en el uso de closures: Aprovecha los closures de manera natural para mantener el estado interno.
Comparación con clases
Aunque las clases de ES6 también permiten crear objetos con datos privados (usando campos privados), las funciones de fábrica ofrecen una alternativa más funcional y a veces más sencilla. Además, las funciones de fábrica pueden ser más flexibles en ciertos casos, ya que no requieren el uso de new
y pueden aprovechar las características de las funciones superiores.
Por ejemplo, es posible combinar funciones de fábrica para crear objetos más complejos:
function withLogging(obj) {
return {
...obj,
log(message) {
console.log(`[${new Date().toISOString()}] ${message}`);
}
};
}
function createProduct(name, price) {
return withLogging({
name,
price,
display() {
this.log(`Product: ${this.name}, Price: ${this.price}`);
}
});
}
const product = createProduct('Laptop', 1500);
product.display(); // Imprime el log con la información del producto
En este ejemplo, se combina la función withLogging
con createProduct
para añadir funcionalidades adicionales al objeto resultante. Esto demuestra la composición de funciones y objetos utilizando funciones de fábrica y closures.
Uso en programación funcional
Las funciones de fábrica y los closures encajan perfectamente en el paradigma de la programación funcional, donde las funciones son ciudadanos de primera clase y se promueve la inmutabilidad y las funciones puras. Aunque los closures permiten mantener estado interno, su uso controlado puede integrarse en aplicaciones funcionales para gestionar datos y comportamientos.
Por ejemplo, se pueden crear funciones generadoras de operaciones:
function createMultiplier(factor) {
return function(number) {
return number * factor;
};
}
const double = createMultiplier(2);
const triple = createMultiplier(3);
console.log(double(5)); // Imprime 10
console.log(triple(5)); // Imprime 15
Aquí, createMultiplier
es una función de fábrica que genera nuevas funciones utilizando closures. Estas funciones pueden ser utilizadas como herramientas en transformaciones funcionales.
En resumen, los closures y las funciones de fábrica son herramientas poderosas en JavaScript que permiten implementar patrones como la encapsulación y el manejo de datos privados. Estos patrones contribuyen a escribir código más seguro, modular y mantenible, aprovechando las características únicas del lenguaje.
Ejercicios de esta lección Funciones cierre (closure)
Evalúa tus conocimientos de esta lección Funciones cierre (closure) con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.
Funciones flecha
Polimorfismo
Array
Transformación con map()
Gestor de tareas con JavaScript
Manipulación DOM
Funciones
Funciones flecha
Async / Await
Creación y uso de variables
Excepciones
Promises
Funciones cierre (closure)
Herencia
Herencia
Estructuras de control
Selección de elementos DOM
Modificación de elementos DOM
Filtrado con filter() y find()
Funciones cierre (closure)
Funciones
Mapas con Map
Reducción con reduce()
Callbacks
Manipulación DOM
Promises
Async / Await
Eventos del DOM
Async / Await
Promises
Filtrado con filter() y find()
Callbacks
Creación de clases y objetos Restaurante
Reducción con reduce()
Filtrado con filter() y find()
Reducción con reduce()
Conjuntos con Set
Herencia de clases
Eventos del DOM
Clases y objetos
Modificación de elementos DOM
Mapas con Map
Introducción a JavaScript
Funciones
Tipos de datos
Clases y objetos
Array
Conjuntos con Set
Array
Encapsulación
Clases y objetos
Uso de operadores
Uso de operadores
Estructuras de control
Excepciones
Transformación con map()
Funciones flecha
Selección de elementos DOM
Encapsulación
Mapas con Map
Creación y uso de variables
Polimorfismo
Tipos de datos
Estructuras de control
Todas las lecciones de JavaScript
Accede a todas las lecciones de JavaScript y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.
Introducción A Javascript
Introducción Y Entorno
Tipos De Datos
Sintaxis
Variables
Sintaxis
Operadores
Sintaxis
Estructuras De Control
Sintaxis
Funciones
Sintaxis
Funciones Cierre (Closure)
Sintaxis
Arrays Y Métodos
Estructuras De Datos
Conjuntos Con Set
Estructuras De Datos
Mapas Con Map
Estructuras De Datos
Funciones Flecha
Programación Funcional
Filtrado Con Filter() Y Find()
Programación Funcional
Transformación Con Map()
Programación Funcional
Reducción Con Reduce()
Programación Funcional
Clases Y Objetos
Programación Orientada A Objetos
Excepciones
Programación Orientada A Objetos
Encapsulación
Programación Orientada A Objetos
Herencia
Programación Orientada A Objetos
Polimorfismo
Programación Orientada A Objetos
Manipulación Dom
Dom
Selección De Elementos Dom
Dom
Modificación De Elementos Dom
Dom
Eventos Del Dom
Dom
Callbacks
Programación Asíncrona
Promises
Programación Asíncrona
Async / Await
Programación Asíncrona
Certificados de superación de JavaScript
Supera todos los ejercicios de programación del curso de JavaScript y obtén certificados de superación para mejorar tu currículum y tu empleabilidad.
En esta lección
Objetivos de aprendizaje de esta lección
- Comprender el concepto de closures y su importancia en JavaScript.
- Conocer cómo se crea un closure y cómo retiene el acceso a su ámbito léxico original.
- Aprender a utilizar closures para crear funciones con contextos específicos y mantener la privacidad de ciertas variables.
- Entender cómo los closures pueden ser útiles para preservar el estado entre llamadas a funciones y permitir una programación más modular y reutilizable.