JavaScript
Closures
Explica cómo una función conserva bindings de su entorno léxico para mantener estado, configuración y datos en callbacks, factories y listeners.
- Última actualización
- Actualizada
- Nivel
- Fundamentos
- Fundamentos de programación
JavaScript
Explica cómo una función conserva bindings de su entorno léxico para mantener estado, configuración y datos en callbacks, factories y listeners.
Un closure ocurre cuando una función conserva acceso a los bindings del entorno léxico donde fue creada, incluso si se ejecuta después de que la función exterior terminó.
function createCounter() {
let count = 0;
return function increment() {
count++;
return count;
};
}
const counter = createCounter();
counter(); // 1
counter(); // 2const language = "JavaScript";
function showLanguage() {
return language;
}showLanguage puede resolver language mediante su scope léxico.
El closure no aparece únicamente cuando una función es retornada. Toda función mantiene la relación con el entorno donde fue creada. Se vuelve especialmente visible cuando esa función se ejecuta más tarde o fuera de ese lugar.
function createCounter() {
let count = 0;
return function increment() {
count++;
return count;
};
}createCounter.count.increment dentro de ese entorno.increment se retorna.count.El binding no se vuelve global. Solo las funciones con acceso léxico pueden utilizarlo.
function createStatusReader() {
let status = "pending";
return {
read() {
return status;
},
complete() {
status = "completed";
},
};
}
const order = createStatusReader();
order.read(); // "pending"
order.complete();
order.read(); // "completed"Las funciones acceden al binding actual de status. No recibieron una copia congelada del string inicial.
const firstCounter = createCounter();
const secondCounter = createCounter();
firstCounter(); // 1
firstCounter(); // 2
secondCounter(); // 1Cada llamada a createCounter crea su propio binding count. Los closures no comparten estado salvo que hayan sido creados dentro del mismo entorno.
function createCounter() {
let count = 0;
return {
increment() {
count++;
},
decrement() {
count--;
},
read() {
return count;
},
};
}Las tres funciones fueron creadas durante la misma llamada y comparten el mismo binding count.
function createSession(token) {
let currentToken = token;
return {
readToken() {
return currentToken;
},
replaceToken(nextToken) {
currentToken = nextToken;
},
clear() {
currentToken = null;
},
};
}No existe acceso directo a currentToken desde fuera. La API retornada controla las operaciones permitidas.
Esto no convierte automáticamente el código en seguro frente a ataques; simplemente limita el acceso mediante scope.
function createApiClient(baseUrl) {
return async function request(path) {
const response = await fetch(`${baseUrl}${path}`);
return response.json();
};
}
const requestProducts = createApiClient("https://api.example.com");
requestProducts("/products");El callback conserva baseUrl sin que cada llamada tenga que enviarlo de nuevo.
function registerProductButton(button, productId) {
button.addEventListener("click", () => {
openProduct(productId);
});
}El listener puede ejecutarse mucho después y aun así conoce productId.
const handlers = [];
for (var index = 0; index < 3; index++) {
handlers.push(() => index);
}
handlers[0](); // 3
handlers[1](); // 3
handlers[2](); // 3var crea un solo binding de función. Todos los callbacks leen ese mismo binding después de que el bucle terminó.
Con let, cada iteración obtiene un binding diferente.
const handlers = [];
for (let index = 0; index < 3; index++) {
handlers.push(() => index);
}
handlers[0](); // 0
handlers[1](); // 1
handlers[2](); // 2Antes de let, una IIFE podía crear un entorno nuevo.
for (var index = 0; index < 3; index++) {
handlers.push(
((currentIndex) => () => currentIndex)(index),
);
}Hoy let comunica mejor la intención.
Un closure puede seguir leyendo un valor que pertenece a un entorno anterior.
function createLogger(message) {
return function log() {
console.log(message);
};
}
const logInitialMessage = createLogger("Initial");
logInitialMessage(); // "Initial"La función no buscará una variable externa diferente llamada message; conserva el binding de la llamada que la creó.
En interfaces reactivas, cada render puede crear funciones que capturan los valores de ese render. Si una función se ejecuta más tarde, puede utilizar datos anteriores. Este fenómeno suele llamarse stale closure.
La solución depende del sistema: actualizar dependencias, leer una referencia mutable, usar una actualización funcional o recrear el callback en el momento correcto.
Mientras una función accesible necesite bindings exteriores, esos datos no pueden liberarse por completo.
function createProcessor(largeDataset) {
return function process() {
return largeDataset.length;
};
}Si process permanece almacenada, también puede mantener accesible largeDataset.
No significa que todos los datos del scope se conserven de forma ingenua; los motores optimizan internamente. El principio útil es que una referencia viva puede extender el tiempo de vida de los datos que necesita.
let handler = createProcessor(largeDataset);
handler();
handler = null;Si no quedan otras referencias relevantes, el entorno y sus datos pueden quedar disponibles para garbage collection.
No se libera memoria manualmente; se eliminan referencias que ya no son necesarias.
No se escribe una palabra especial para crear un closure. Surge del scope léxico cuando una función utiliza bindings exteriores.
function outer() {
const value = 10;
return () => value;
}La estructura del código ya crea la relación.
function requirePermission(permission) {
return function checkPermission(user) {
return user.permissions.includes(permission);
};
}
const canDeleteProducts = requirePermission("products:delete");
const canManageUsers = requirePermission("users:manage");
canDeleteProducts(currentUser);
canManageUsers(currentUser);Cada función conserva una configuración diferente.
var en bucles y esperar un binding por iteración.let crea un binding por iteración en bucles.¿Qué devuelven estas llamadas?
function createCounter() {
let count = 0;
return () => ++count;
}
const first = createCounter();
const second = createCounter();
first();
first();
second();Devuelven 1, 2 y 1. first y second conservan bindings count pertenecientes a llamadas diferentes.
Recursividad estudia funciones que resuelven un problema llamándose a sí mismas y cómo esas llamadas crecen dentro del Call Stack.