función - ¿Cómo funcionan los closures en JavaScript?
¿Cómo explicarías los closures de JavaScript a alguien que tiene conocimiento de los conceptos de los que se componen (por ejemplo, funciones, variables y similares), pero no entiende los closures en sí?
He visto el ejemplo de Scheme dado en Wikipedia, pero desafortunadamente no ayudó.
Respuesta #1
Un closure es una combinación de:
- Una función y
- Una referencia al ámbito externo de esa función (entorno léxico)
Un entorno léxico es parte de cada contexto de ejecución (marco de pila) y es un mapa entre identificadores (es decir, nombres de variables locales) y valores.
Cada función en JavaScript mantiene una referencia a su entorno léxico externo. Esta referencia se utiliza para configurar el contexto de ejecución creado cuando se invoca una función. Esta referencia permite que el código dentro de la función "vea" las variables declaradas fuera de la función, sin importar cuándo y dónde se llame a la función.
Si una función fue llamada por otra función, la cual a su vez fue llamada por otra función, entonces se crea una cadena de referencias a entornos léxicos externos. Esta cadena se llama cadena de alcance.
En el siguiente código, inner forma un cierre con el entorno léxico del contexto de ejecución creado cuando se invoca foo, capturando la variable secret:
function foo() {
const secret = Math.trunc(Math.random() * 100)
return function inner() {
console.log(`The secret number is ${secret}.`)
}
}
const f = foo() // `secret` is not directly accessible from outside `foo`
f() // The only way to retrieve `secret` is to invoke `f`En otras palabras: en JavaScript, las funciones llevan una referencia a una 'caja de estado' privada, a la cual solo ellas (y cualquier otra función declarada dentro del mismo entorno léxico) tienen acceso. Esta caja del estado es invisible para quien llama a la función, proporcionando un excelente mecanismo para ocultar datos y encapsulación.
Y recuerda: las funciones en JavaScript pueden ser pasadas como variables (funciones de primera clase), lo que significa que estos emparejamientos de funcionalidad y estado pueden ser pasados a lo largo de tu programa, de manera similar a como podrías pasar una instancia de una clase en C++.
Si JavaScript no tuviera cierres, entonces más estados tendrían que ser pasados entre funciones explícitamente, haciendo que las listas de parámetros sean más largas y el código más ruidoso.
Entonces, si quieres que una función siempre tenga acceso a una parte privada del estado, puedes usar un cierre.
...y frecuentemente sí queremos asociar el estado con una función. Por ejemplo, en Java o C++, cuando agregas una variable de instancia privada y un método a una clase, estás asociando el estado con la funcionalidad.
En C y en la mayoría de los otros lenguajes comunes, después de que una función retorna, todas las variables locales dejan de ser accesibles porque el marco de pila se destruye. En JavaScript, si declaras una función dentro de otra función, entonces las variables locales de la función externa pueden permanecer accesibles después de retornarla. De esta manera, en el código anterior, secret permanece disponible para el objeto función inner, después de que haya retornado de foo.
Usos de los cierres
Los closures son útiles siempre que necesites un estado privado asociado a una función. Este es un escenario muy común, y recuerda: JavaScript no tuvo una sintaxis de clase hasta 2015, y todavía no tiene una sintaxis de campos privados. Los closures satisfacen esta necesidad.
Variables de instancia privadas
En el siguiente código, la función toString cierra sobre los detalles del coche.
function Car(manufacturer, model, year, color) {
return {
toString() {
return `${manufacturer} ${model} (${year}, ${color})`
}
}
}
const car = new Car('Aston Martin', 'V8 Vantage', '2012', 'Quantum Silver')
console.log(car.toString())Programación Funcional
En el siguiente código, la función inner cierra sobre tanto fn como args.
function curry(fn) {
const args = []
return function inner(arg) {
if(args.length === fn.length) return fn(...args)
args.push(arg)
return inner
}
}
function add(a, b) {
return a + b
}
const curriedAdd = curry(add)
console.log(curriedAdd(2)(3)()) // 5Programación Orientada a Eventos
En el siguiente código, la función onClick cierra sobre la variable BACKGROUND_COLOR.
const $ = document.querySelector.bind(document)
const BACKGROUND_COLOR = 'rgba(200, 200, 242, 1)'
function onClick() {
$('body').style.background = BACKGROUND_COLOR
}
$('button').addEventListener('click', onClick)<button>Set background color</button>Modularización
En el siguiente ejemplo, todos los detalles de implementación están ocultos dentro de una expresión de función ejecutada inmediatamente. Las funciones tick y toString cierran sobre el estado privado y las funciones que necesitan para completar su trabajo. Las clausuras nos han permitido modularizar y encapsular nuestro código.
let namespace = {};
(function foo(n) {
let numbers = []
function format(n) {
return Math.trunc(n)
}
function tick() {
numbers.push(Math.random() * 100)
}
function toString() {
return numbers.map(format)
}
n.counter = {
tick,
toString
}
}(namespace))
const counter = namespace.counter
counter.tick()
counter.tick()
console.log(counter.toString())Ejemplos
Ejemplo 1
Este ejemplo muestra que las variables locales no se copian en el cierre: el cierre mantiene una referencia a las propias variables originales. Es como si el marco de pila permaneciera vivo en la memoria incluso después de que la función externa termine.
function foo() {
let x = 42
let inner = () => console.log(x)
x = x + 1
return inner
}
foo()() // logs 43Ejemplo 2
En el siguiente código, tres métodos log, increment y update cierran sobre el mismo entorno léxico.
Y cada vez que se llama a createObject, se crea un nuevo contexto de ejecución (marco de pila) y una variable completamente nueva x, y se crea un nuevo conjunto de funciones (log etc.) que cierran sobre esta nueva variable.
function createObject() {
let x = 42;
return {
log() { console.log(x) },
increment() { x++ },
update(value) { x = value }
}
}
const o = createObject()
o.increment()
o.log() // 43
o.update(5)
o.log() // 5
const p = createObject()
p.log() // 42Ejemplo 3
Si estás usando variables declaradas con var, ten cuidado de entender qué variable estás cerrando. Las variables declaradas con var son elevadas (hoisted). Esto es mucho menos problemático en JavaScript moderno debido a la introducción de let y const.
En el siguiente código, cada vez que se ejecuta el bucle, se crea una nueva función inner, que cierra sobre i. Pero debido a que var i se eleva fuera del bucle, todas estas funciones internas cierran sobre la misma variable, lo que significa que el valor final de i (3) se imprime tres veces.
function foo() {
var result = []
for (var i = 0; i < 3; i++) {
result.push(function inner() { console.log(i) } )
}
return result
}
const result = foo()
// The following will print `3`, three times...
for (var i = 0; i < 3; i++) {
result[i]()
}Puntos finales:
- Siempre que se declara una función en JavaScript se crea un cierre.
- Devolver una
funcióndesde dentro de otra función es el ejemplo clásico de cierre, porque el estado dentro de la función externa está disponible implícitamente para la función interna devuelta, incluso después de que la función externa haya completado su ejecución. - Siempre que uses
eval()dentro de una función, se utiliza un closure. El texto queevalúaspuede hacer referencia a las variables locales de la función, y en el modo no estricto, incluso puedes crear nuevas variables locales usandoeval('var foo=…'). - Cuando usas
new Function(…)(el constructor Function) dentro de una función, no cierra sobre su entorno léxico: cierra sobre el contexto global en su lugar. La nueva función no puede referenciar las variables locales de la función externa. - Un closure en JavaScript es como mantener una referencia (NO una copia) al alcance en el punto de declaración de la función, que a su vez mantiene una referencia a su alcance externo, y así sucesivamente, todo el camino hasta el objeto global en la parte superior de la cadena de alcances.
- Se crea un cierre cuando se declara una función; este cierre se utiliza para configurar el contexto de ejecución cuando se invoca la función.
- Se crea un nuevo conjunto de variables locales cada vez que se llama a una función.
Enlaces
- Douglas Crockford simuló atributos privados y métodos privados para un objeto, usando closures.
- Una gran explicación de cómo los cierres pueden causar fugas de memoria en IE si no tienes cuidado.
- Documentación de MDN sobre cierres en JavaScript.
- La guía para principiantes sobre los closures en JavaScript.
Respuesta #2
Cada función en JavaScript mantiene un vínculo con su entorno léxico externo. Un entorno léxico es un mapa de todos los nombres (por ejemplo, variables, parámetros) dentro de un ámbito, con sus valores.
Entonces, cada vez que veas la palabra clave function, el código dentro de esa función tiene acceso a las variables declaradas fuera de la función.
function foo(x) {
var tmp = 3;
function bar(y) {
console.log(x + y + (++tmp)); // will log 16
}
bar(10);
}
foo(2);Esto registrará 16 porque la función bar cierra sobre el parámetro x y la variable tmp, ambos existen en el entorno léxico de la función externa foo.
La función bar, junto con su vínculo con el entorno léxico de la función foo, es un cierre.
Una función no tiene que devolver para crear un cierre. Simplemente por el hecho de su declaración, cada función se cierra sobre su entorno léxico encerrante, formando un cierre.
function foo(x) {
var tmp = 3;
return function (y) {
console.log(x + y + (++tmp)); // will also log 16
}
}
var bar = foo(2);
bar(10); // 16
bar(10); // 17La función anterior también registrará 16, porque el código dentro de bar todavía puede referirse al argumento x y a la variable tmp, aunque ya no estén directamente en el alcance.
Sin embargo, dado que tmp todavía permanece dentro del cierre de bar, está disponible para ser incrementado. Se incrementará cada vez que llames a bar.
El ejemplo más simple de un cierre es este:
var a = 10;
function test() {
console.log(a); // will output 10
console.log(b); // will output 6
}
var b = 6;
test();Cuando se invoca una función de JavaScript, se crea un nuevo contexto de ejecución ec. Junto con los argumentos de la función y el objeto objetivo, este contexto de ejecución también recibe un enlace al entorno léxico del contexto de ejecución que la llama, lo que significa que las variables declaradas en el entorno léxico externo (en el ejemplo anterior, tanto a como b) están disponibles desde ec.
Cada función crea un cierre porque cada función tiene un enlace a su entorno léxico exterior.
Tenga en cuenta que las variables mismas son visibles desde dentro de un cierre, no copias.
Respuesta #3
PRÓLOGO: esta respuesta fue escrita cuando la pregunta era:
Como decía el viejo Albert: "Si no puedes explicárselo a un niño de seis años, realmente no lo entiendes tú mismo." Bueno, traté de explicarle los closures de JS a un amigo de 27 años y fracasé completamente.
¿Puede alguien considerar que tengo 6 años y que estoy extrañamente interesado en ese tema?
Estoy bastante seguro de que fui una de las pocas personas que intentó tomar la pregunta inicial literalmente. Desde entonces, la pregunta ha mutado varias veces, por lo que mi respuesta ahora puede parecer increíblemente tonta y fuera de lugar. Espero que la idea general de la historia siga siendo divertida para algunos.
Soy un gran fan de la analogía y la metáfora al explicar conceptos difíciles, así que déjame intentar con una historia.
Érase una vez:
Había una princesa...
function princess() {
Ella vivía en un mundo maravilloso lleno de aventuras. Conoció a su príncipe encantador, cabalgó por su mundo en un unicornio, luchó contra dragones, encontró animales que hablan y muchas otras cosas fantásticas.
var adventures = [];
function princeCharming() { /* ... */ }
var unicorn = { /* ... */ },
dragons = [ /* ... */ ],
squirrel = "Hello!";
/* ... */
Pero ella siempre tendría que regresar a su aburrido mundo de tareas y adultos.
return {
Y a menudo les contaba sobre su última increíble aventura como princesa.
story: function() {
return adventures[adventures.length - 1];
}
};
}
Pero todo lo que verían es a una niña pequeña...
var littleGirl = princess();
...contando historias sobre magia y fantasía.
littleGirl.story();
Y aunque los adultos conocían a verdaderas princesas, nunca creerían en los unicornios o dragones porque nunca podían verlos. Los adultos decían que solo existían dentro de la imaginación de la niña.
Pero sabemos la verdad real; que la niña con la princesa dentro...
...es realmente una princesa con una niña en su interior.
Respuesta #4
Tomando la pregunta en serio, deberíamos averiguar de qué es capaz cognitivamente un niño típico de 6 años, aunque, dicho sea de paso, uno que esté interesado en JavaScript no es tan típico.
Sobre el Desarrollo Infantil: de 5 a 7 años dice:
Tu hijo podrá seguir instrucciones de dos pasos. Por ejemplo, si le dices a tu hijo: "Ve a la cocina y tráeme una bolsa de basura", podrá recordar esa instrucción.
Podemos usar este ejemplo para explicar cierres, de la siguiente manera:
La cocina es un cierre que tiene una variable local, llamada
trashBags. Hay una función dentro de la cocina llamadagetTrashBagque obtiene una bolsa de basura y la devuelve.
Podemos programar esto en JavaScript así:
function makeKitchen() {
var trashBags = ['A', 'B', 'C']; // only 3 at first
return {
getTrashBag: function() {
return trashBags.pop();
}
};
}
var kitchen = makeKitchen();
console.log(kitchen.getTrashBag()); // returns trash bag C
console.log(kitchen.getTrashBag()); // returns trash bag B
console.log(kitchen.getTrashBag()); // returns trash bag AOtros puntos que explican por qué los cierres son interesantes:
- Cada vez que se llama a
makeKitchen(), se crea un nuevo cierre con sus propiastrashBagsseparadas. - La variable
trashBagses local al interior de cada cocina y no es accesible desde fuera, pero la función interna en la propiedadgetTrashBagsí tiene acceso a ella. - Cada llamada a una función crea un cierre, pero no habría necesidad de mantener el cierre a menos que se pueda llamar a una función interna, que tiene acceso al interior del cierre, desde fuera del cierre. Devolver el objeto con la función
getTrashBaghace eso aquí.
Respuesta #5
El Hombre de Paja
Necesito saber cuántas veces se ha hecho clic en un botón y hacer algo en cada tercer clic...
Solución Bastante Obvia
// Declare counter outside event handler's scope
var counter = 0;
var element = document.getElementById('button');
element.addEventListener("click", function() {
// Increment outside counter
counter++;
if (counter === 3) {
// Do something every third time
console.log("Third time's the charm!");
// Reset counter
counter = 0;
}
});<button id="button">Click Me!</button>Ahora esto funcionará, pero invade el ámbito externo al agregar una variable, cuyo único propósito es llevar el conteo. En algunas situaciones, esto sería preferible, ya que tu aplicación externa podría necesitar acceso a esta información. Pero en este caso, solo estamos cambiando el comportamiento de cada tercer clic, por lo que es preferible encerrar esta funcionalidad dentro del manejador de eventos.
Considera esta opción
var element = document.getElementById('button');
element.addEventListener("click", (function() {
// init the count to 0
var count = 0;
return function(e) { // <- This function becomes the click handler
count++; // and will retain access to the above `count`
if (count === 3) {
// Do something every third time
console.log("Third time's the charm!");
//Reset counter
count = 0;
}
};
})());<button id="button">Click Me!</button>Observa algunas cosas aquí.
En el ejemplo anterior, estoy usando el comportamiento de cierre de JavaScript. Este comportamiento permite que cualquier función tenga acceso al ámbito en el que fue creada, indefinidamente. Para aplicar esto prácticamente, invoco inmediatamente una función que devuelve otra función, y debido a que la función que estoy devolviendo tiene acceso a la variable interna count (debido al comportamiento de cierre explicado arriba), esto resulta en un ámbito privado para ser usado por la función resultante... ¿No tan simple? Vamos a simplificarlo...
Un cierre simple de una sola línea
// _______________________Immediately invoked______________________
// | |
// | Scope retained for use ___Returned as the____ |
// | only by returned function | value of func | |
// | | | | | |
// v v v v v v
var func = (function() { var a = 'val'; return function() { alert(a); }; })();
Todas las variables fuera de la función devuelta están disponibles para la función devuelta, pero no están directamente disponibles para el objeto de la función devuelta...
func(); // Alerts "val"
func.a; // Undefined
¿Lo entiendes? Así que en nuestro ejemplo principal, la variable count está contenida dentro del closure y siempre disponible para el manejador de eventos, por lo que mantiene su estado de clic en clic.
Además, esta variable privada state es completamente accesible, tanto para lecturas como para asignaciones a sus variables de ámbito privado.
Ahí tienes; ahora estás encapsulando completamente este comportamiento.
Publicación completa del blog (incluyendo consideraciones sobre jQuery)
Respuesta #6
Los closures son difíciles de explicar porque se usan para hacer que cierto comportamiento funcione que todo el mundo espera intuitivamente que funcione de todos modos. Encuentro que la mejor manera de explicarlos (y la manera en que YO aprendí lo que hacen) es imaginar la situación sin ellos:
const makePlus = function(x) {
return function(y) { return x + y; };
}
const plus5 = makePlus(5);
console.log(plus5(3));¿Qué pasaría aquí si JavaScript no conociera los closures? Simplemente reemplaza la llamada en la última línea por el cuerpo de su método (que es básicamente lo que hacen las llamadas a funciones) y obtienes:
console.log(x + 3);
Ahora, ¿dónde está la definición de x? No la definimos en el ámbito actual. La única solución es permitir que plus5 lleve su ámbito (o más bien, el ámbito de su padre) consigo. De esta manera, x está bien definido y está ligado al valor 5.
Respuesta #7
Resumen
Un cierre es un enlace entre una función y su entorno léxico externo (es decir, tal como está escrito), de manera que los identificadores (variables, parámetros, declaraciones de funciones, etc.) definidos dentro de ese entorno son visibles desde dentro de la función, independientemente de cuándo o desde dónde se invoque la función.
Detalles
En la terminología de la especificación ECMAScript, se puede decir que un cierre se implementa mediante la referencia [[Environment]] de cada objeto-función, que apunta al entorno léxico dentro del cual se define la función.
Cuando se invoca una función a través del método interno [[Call]], la referencia [[Environment]] del objeto función se copia en la referencia del entorno externo del registro de entorno del contexto de ejecución recién creado (marco de pila).
En el siguiente ejemplo, la función f cierra sobre el entorno léxico del contexto de ejecución global:
function f() {}
En el siguiente ejemplo, la función h cierra sobre el entorno léxico de la función g, que, a su vez, cierra sobre el entorno léxico del contexto de ejecución global.
function g() {
function h() {}
}
Si una función interna es devuelta por una externa, entonces el entorno léxico externo persistirá después de que la función externa haya retornado. Esto se debe a que el entorno léxico externo necesita estar disponible si la función interna eventualmente es invocada.
En el siguiente ejemplo, la función j cierra sobre el entorno léxico de la función i, lo que significa que la variable x es visible desde el interior de la función j, mucho después de que la función i haya completado su ejecución:
function i() {
var x = 'mochacchino'
return function j() {
console.log('Printing the value of x, from within function j: ', x)
}
}
const k = i()
setTimeout(k, 500) // invoke k (which is j) after 500msEn un closure, las variables en el entorno léxico externo ellos mismos están disponibles, no copias.
function l() {
var y = 'vanilla';
return {
setY: function(value) {
y = value;
},
logY: function(value) {
console.log('The value of y is: ', y);
}
}
}
const o = l()
o.logY() // The value of y is: vanilla
o.setY('chocolate')
o.logY() // The value of y is: chocolateLa cadena de entornos léxicos, vinculados entre contextos de ejecución mediante referencias al entorno externo, forma una cadena de alcance y define los identificadores visibles desde cualquier función dada.
Tenga en cuenta que, en un intento por mejorar la claridad y la precisión, esta respuesta ha sido sustancialmente modificada respecto al original.
Respuesta #8
Está bien, fan de los closures de 6 años. ¿Quieres escuchar el ejemplo más simple de closure?
Imaginemos la siguiente situación: un conductor está sentado en un coche. Ese coche está dentro de un avión. El avión está en el aeropuerto. La capacidad del conductor para acceder a cosas fuera de su coche, pero dentro del avión, incluso si ese avión sale del aeropuerto, es un cierre. Eso es todo. Cuando cumplas 27 años, mira la explicación más detallada o el ejemplo a continuación.
Aquí está cómo puedo convertir mi historia del avión en el código.
var plane = function(defaultAirport) {
var lastAirportLeft = defaultAirport;
var car = {
driver: {
startAccessPlaneInfo: function() {
setInterval(function() {
console.log("Last airport was " + lastAirportLeft);
}, 2000);
}
}
};
car.driver.startAccessPlaneInfo();
return {
leaveTheAirport: function(airPortName) {
lastAirportLeft = airPortName;
}
}
}("Boryspil International Airport");
plane.leaveTheAirport("John F. Kennedy");Respuesta #9
Este es un intento de aclarar varios (posibles) malentendidos sobre los cierres que aparecen en algunas de las otras respuestas.
- Un cierre no solo se crea cuando devuelves una función interna. De hecho, la función contenedora no necesita devolver nada en absoluto para que se cree su cierre. En su lugar, podrías asignar tu función interna a una variable en un ámbito exterior, o pasarla como argumento a otra función donde podría ser llamada inmediatamente o en cualquier momento posterior. Por lo tanto, el cierre de la función contenedora probablemente se cree tan pronto como se llame a la función contenedora ya que cualquier función interna tiene acceso a ese cierre siempre que se llame a la función interna, antes o después de que la función contenedora devuelva.
- Un cierre no hace referencia a una copia de los valores antiguos de las variables en su alcance. Las propias variables son parte del cierre, por lo que el valor que se ve al acceder a una de esas variables es el valor más reciente en el momento en que se accede. Por eso, las funciones internas creadas dentro de bucles pueden ser complicadas, ya que cada una tiene acceso a las mismas variables externas en lugar de tomar una copia de las variables en el momento en que se crea o se llama la función.
- Las "variables" en un cierre incluyen cualquier función nombrada declarada dentro de la función. También incluyen los argumentos de la función. Un cierre también tiene acceso a las variables de su cierre contenedor, hasta el alcance global.
- Los cierres usan memoria, pero no causan fugas de memoria ya que JavaScript por sí mismo limpia sus propias estructuras circulares que no están referenciadas. Las fugas de memoria en Internet Explorer que involucran cierres se crean cuando no logra desconectar valores de atributos del DOM que hacen referencia a cierres, manteniendo así referencias a estructuras posiblemente circulares.
Respuesta #10
Escribí una entrada de blog hace un tiempo explicando los cierres. Esto es lo que dije sobre los cierres en términos de por qué querrías uno.
Los closures son una forma de permitir que una función tenga variables privadas y persistentes, es decir, variables que solo una función conoce, donde puede mantener un registro de la información de veces anteriores en que se ejecutó.
En ese sentido, permiten que una función actúe un poco como un objeto con atributos privados.
Ejemplo
var iplus1= (function ()
{
var plusCount = 0;
return function ()
{
return ++plusCount;
}
})();
Aquí, la función anónima autoejecutable exterior se ejecuta solo una vez y establece la variable plusCount en 0, y retorna la expresión de la función interna.
Mientras que la función interna tiene acceso a la variable plusCount. Ahora, cada vez que invocamos la función iplus1(), la función interna incrementa la variable plusCount.
El punto importante a tener en cuenta es que ningún otro script en la página tiene acceso a la variable plusCount y la única manera de cambiar la variable plusCount es a través de la función iplus1.
Lee más para referencia: Entonces, ¿qué son estas cosas de cierre?
Respuesta #11
La pregunta original tenía una cita:
Si no puedes explicárselo a un niño de seis años, realmente no lo entiendes tú mismo.
Así es como trataría de explicárselo a un niño de seis años:
¿Sabes cómo los adultos pueden ser dueños de una casa y la llaman hogar? Cuando una mamá tiene un hijo, el hijo realmente no posee nada, ¿verdad? Pero sus padres son dueños de una casa, así que cada vez que alguien le pregunta "¿Dónde está tu hogar?", el niño puede responder "¡esa casa!" y señalar la casa de sus padres.
Un "Cierre" es la capacidad del niño de poder siempre (incluso si está en el extranjero) referirse a su hogar, aunque en realidad sean los padres quienes poseen la casa.
Respuesta #12
Los cierres son simples:
El siguiente ejemplo simple cubre todos los puntos principales de los closures en JavaScript.*
Aquí hay una fábrica que produce calculadoras que pueden sumar y multiplicar:
function make_calculator() {
var n = 0; // this calculator stores a single number n
return {
add: function(a) {
n += a;
return n;
},
multiply: function(a) {
n *= a;
return n;
}
};
}
first_calculator = make_calculator();
second_calculator = make_calculator();
first_calculator.add(3); // returns 3
second_calculator.add(400); // returns 400
first_calculator.multiply(11); // returns 33
second_calculator.multiply(10); // returns 4000
El punto clave: Cada llamada a make_calculator crea una nueva variable local n, que sigue siendo utilizable por las funciones add y multiply de ese calculador mucho después de que make_calculator retorna.
Si estás familiarizado con los marcos de pila, estas calculadoras parecen extrañas: ¿Cómo pueden seguir accediendo a n después de que make_calculator devuelve? La respuesta es imaginar que JavaScript no usa "marcos de pila", sino que usa "marcos de montón", que pueden persistir después de que la llamada a la función que los creó devuelve.
Las funciones internas como add y multiply, que acceden a variables declaradas en una función externa**, se llaman closures.
Eso es prácticamente todo lo que hay sobre los closures.
(Hablando estrictamente, la función interna accede a la copia de la variable de la función auxiliar, en lugar de tener algo conectado.) De nuevo, muy útil al crear cierres, pero no es parte de lo que un cierre es ni de cómo funciona. Hay confusión adicional debido a que los cierres funcionan de manera diferente en lenguajes funcionales como ML, donde las variables están vinculadas a valores en lugar de al espacio de almacenamiento, proporcionando un flujo constante de personas que entienden los cierres de una manera (a saber, la forma de “conectar”) que simplemente es incorrecta para JavaScript, donde las variables siempre están vinculadas al espacio de almacenamiento, y nunca a los valores.
** Cualquier función exterior, si varias están anidadas, o incluso en el contexto global, como esta respuesta señala claramente.
Respuesta #13
¿Puedes explicar los cierres a un niño de 5 años?
Todavía creo que la explicación de Google funciona muy bien y es concisa:
/*
* When a function is defined in another function and it
* has access to the outer function's context even after
* the outer function returns.
*
* An important concept to learn in JavaScript.
*/
function outerFunction(someNum) {
var someString = 'Hey!';
var content = document.getElementById('content');
function innerFunction() {
content.innerHTML = someNum + ': ' + someString;
content = null; // Internet Explorer memory leak for DOM reference
}
innerFunction();
}
outerFunction(1);
*Una pregunta de C#
Respuesta #14
Tiendo a aprender mejor mediante comparaciones BUENAS/MALAS. Me gusta ver código que funciona seguido de código que no funciona y que alguien probablemente se encontrará. He preparado un jsFiddle que hace una comparación e intenta reducir las diferencias a las explicaciones más simples que pude idear.
Cierres hechos correctamente:
console.log('CLOSURES DONE RIGHT');
var arr = [];
function createClosure(n) {
return function () {
return 'n = ' + n;
}
}
for (var index = 0; index < 10; index++) {
arr[index] = createClosure(index);
}
for (var index of arr) {
console.log(arr[index]());
}
En el código anterior,
createClosure(n)se invoca en cada iteración del bucle. Tenga en cuenta que nombré la variablenpara resaltar que es una variable nueva creada en un nuevo ámbito de función y no es la misma variable queindex, la cual está vinculada al ámbito externo.Esto crea un nuevo ámbito y
nestá vinculado a ese ámbito; esto significa que tenemos 10 ámbitos separados, uno para cada iteración.createClosure(n)devuelve una función que devuelve el n dentro de ese ámbito.Dentro de cada ámbito,
nestá ligado al valor que tenía cuando se invocócreateClosure(n), por lo que la función anidada que se devuelve siempre retornará el valor denque tenía cuando se invocócreateClosure(n).
Cierres hechos mal:
console.log('CLOSURES DONE WRONG');
function createClosureArray() {
var badArr = [];
for (var index = 0; index < 10; index++) {
badArr[index] = function () {
return 'n = ' + index;
};
}
return badArr;
}
var badArr = createClosureArray();
for (var index of badArr) {
console.log(badArr[index]());
}
En el código anterior, el bucle se movió dentro de la función
createClosureArray()y la función ahora solo devuelve el array completo, lo que a primera vista parece más intuitivo.Lo que podría no ser obvio es que, dado que
createClosureArray()se invoca solo una vez, solo se crea un ámbito para esta función en lugar de uno para cada iteración del bucle.Dentro de esta función se define una variable llamada
index. El bucle se ejecuta y agrega funciones al array que devuelvenindex. Nota queindexse define dentro de la funcióncreateClosureArrayque solo se invoca una vez.Debido a que solo había un ámbito dentro de la función
createClosureArray(),indexsolo está ligado a un valor dentro de ese ámbito. En otras palabras, cada vez que el bucle cambia el valor deindex, lo cambia para todo lo que lo referencia dentro de ese ámbito.Todas las funciones añadidas al arreglo devuelven la MISMA variable
indexdel ámbito padre donde se definió en lugar de 10 diferentes de 10 ámbitos diferentes como en el primer ejemplo. El resultado final es que las 10 funciones devuelven la misma variable del mismo ámbito.Después de que el bucle terminó y
indexdejó de ser modificado, el valor final era 10, por lo tanto, cada función añadida al arreglo devuelve el valor de la única variableindex, que ahora está establecida en 10.
Resultado
CIERRES HECHOS BIEN
n=0
n=1
n=2
n=3
n=4
n=5
n=6
n=7
n=8
n=9CIERRES HECHOS MAL
n=10
n=10
n=10
n=10
n=10
n=10
n=10
n=10
n=10
n=10
Respuesta #15
Wikipedia sobre cierres:
En ciencias de la computación, un cierre es una función junto con un entorno de referencia para los nombres no locales (variables libres) de esa función.
Técnicamente, en JavaScript, toda función es un cierre. Siempre tiene acceso a las variables definidas en el ámbito circundante.
Dado que la construcción que define el alcance en JavaScript es una función, no un bloque de código como en muchos otros lenguajes, lo que usualmente queremos decir con closure en JavaScript es una función que trabaja con variables no locales definidas en la función circundante ya ejecutada.
Los cierres se usan a menudo para crear funciones con algunos datos privados ocultos (pero no siempre es el caso).
var db = (function() {
// Create a hidden object, which will hold the data
// it's inaccessible from the outside.
var data = {};
// Make a function, which will provide some access to the data.
return function(key, val) {
if (val === undefined) { return data[key] } // Get
else { return data[key] = val } // Set
}
// We are calling the anonymous surrounding function,
// returning the above inner function, which is a closure.
})();
db('x') // -> undefined
db('x', 1) // Set x to 1
db('x') // -> 1
// It's impossible to access the data object itself.
// We are able to get or set individual it.
ems
El ejemplo anterior está usando una función anónima, que se ejecutó una vez. Pero no tiene que ser así. Puede ser nombrada (por ejemplo, mkdb) y ejecutada más tarde, generando una función de base de datos cada vez que se invoca. Cada función generada tendrá su propio objeto de base de datos oculto. Otro ejemplo de uso de closures es cuando no devolvemos una función, sino un objeto que contiene múltiples funciones para diferentes propósitos, cada una de esas funciones teniendo acceso a los mismos datos.
Respuesta #16
Los niños nunca olvidarán los secretos que han compartido con sus padres, incluso después de que sus padres se hayan ido. Esto es para lo que sirven los cierres en las funciones.
Los secretos de las funciones de JavaScript son las variables privadas
var parent = function() {
var name = "Mary"; // secret
}
Cada vez que lo llamas, se crea la variable local "name" y se le da el nombre "Mary". Y cada vez que la función termina, la variable se pierde y se olvida el nombre.
Como puedes imaginar, debido a que las variables se recrean cada vez que se llama a la función, y nadie más las conocerá, debe haber un lugar secreto donde se almacenan. Podría llamarse Cámara de los Secretos o pila o ámbito local, pero no importa. Sabemos que están allí, en algún lugar, escondidas en la memoria.
Pero, en JavaScript, existe esta cosa muy especial de que las funciones que se crean dentro de otras funciones, también pueden conocer las variables locales de sus padres y mantenerlas mientras existan.
var parent = function() {
var name = "Mary";
var child = function(childName) {
// I can also see that "name" is "Mary"
}
}
Entonces, mientras estemos en la función padre, puede crear una o más funciones hijas que compartan las variables secretas del lugar secreto.
Pero lo triste es que, si el hijo también es una variable privada de su función padre, también moriría cuando el padre termine, y los secretos morirían con ellos.
Así que para vivir, el niño tiene que irse antes de que sea demasiado tarde
var parent = function() {
var name = "Mary";
var child = function(childName) {
return "My name is " + childName +", child of " + name;
}
return child; // child leaves the parent ->
}
var child = parent(); // < - and here it is outside
Y ahora, aunque Mary ya "no está corriendo", el recuerdo de ella no se ha perdido y su hijo siempre recordará su nombre y otros secretos que compartieron durante su tiempo juntos.
Entonces, si llamas a la niña "Alicia", ella responderá
child("Alice") => "My name is Alice, child of Mary"
Eso es todo lo que hay que contar.
Respuesta #17
He creado un tutorial interactivo en JavaScript para explicar cómo funcionan los closures. ¿Qué es un Closure?
Aquí hay uno de los ejemplos:
var create = function (x) {
var f = function () {
return x; // We can refer to x here!
};
return f;
};
// 'create' takes one argument, creates a function
var g = create(42);
// g is a function that takes no arguments now
var y = g();
// y is 42 here
Respuesta #18
No entiendo por qué las respuestas son tan complejas aquí.
Aquí hay un cierre:
var a = 42;
function b() { return a; }
Sí. Probablemente lo uses muchas veces al día.
No hay razón para creer que los closures sean un truco de diseño complejo para abordar problemas específicos. No, los closures se tratan simplemente de usar una variable que proviene de un ámbito superior desde la perspectiva de donde se declaró la función (no donde se ejecuta).
Ahora lo que te permite hacer puede ser más espectacular, mira otras respuestas.
Respuesta #19
Un cierre es donde una función interna tiene acceso a las variables de su función externa. Probablemente esa sea la explicación de una sola línea más simple que se puede dar sobre los cierres.
Y la función interna tiene acceso no solo a las variables de la función externa, sino también a los parámetros de la función externa como en el ejemplo siguiente
Ejemplo
// Closure Example
function addNumbers(firstNumber, secondNumber)
{
var returnValue = "Result is : ";
// This inner function has access to the outer function's variables & parameters
function add()
{
return returnValue + (firstNumber + secondNumber);
}
return add();
}
var result = addNumbers(10, 20);
console.log(result); //30
Respuesta #20
Ejemplo para el primer punto por dlaliberte:
Un cierre no solo se crea cuando devuelves una función interna. De hecho, la función que lo contiene no necesita devolver nada en absoluto. En cambio, podrías asignar tu función interna a una variable en un ámbito externo, o pasarla como argumento a otra función donde podría ser utilizada inmediatamente. Por lo tanto, el cierre de la función que lo contiene probablemente ya existe en el momento en que se llamó a dicha función, ya que cualquier función interna tiene acceso a él tan pronto como se llama.
var i;
function foo(x) {
var tmp = 3;
i = function (y) {
console.log(x + y + (++tmp));
}
}
foo(2);
i(3);
Respuesta #21
Sé que ya hay muchas soluciones, pero supongo que este script pequeño y simple puede ser útil para demostrar el concepto:
// makeSequencer will return a "sequencer" function
var makeSequencer = function() {
var _count = 0; // not accessible outside this function
var sequencer = function () {
return _count++;
}
return sequencer;
}
var fnext = makeSequencer();
var v0 = fnext(); // v0 = 0;
var v1 = fnext(); // v1 = 1;
var vz = fnext._count // vz = undefined
Respuesta #22
Vas a tener una pijamada y invitas a Dan. Le dices a Dan que traiga un control de Xbox.
Dan invita a Paul. Dan le pide a Paul que traiga un controlador. ¿Cuántos controladores se trajeron a la fiesta?
function sleepOver(howManyControllersToBring) {
var numberOfDansControllers = howManyControllersToBring;
return function danInvitedPaul(numberOfPaulsControllers) {
var totalControllers = numberOfDansControllers + numberOfPaulsControllers;
return totalControllers;
}
}
var howManyControllersToBring = 1;
var inviteDan = sleepOver(howManyControllersToBring);
// The only reason Paul was invited is because Dan was invited.
// So we set Paul's invitation = Dan's invitation.
var danInvitedPaul = inviteDan(howManyControllersToBring);
alert("There were " + danInvitedPaul + " controllers brought to the party.");
Respuesta #23
El autor de Closures ha explicado los closures bastante bien, explicando la razón por la que los necesitamos y también explicando LexicalEnvironment, lo cual es necesario para entender los closures.
Aquí está el resumen:
¿Qué pasa si se accede a una variable, pero no es local? Como aquí:
En este caso, el intérprete encuentra la variable en el objeto LexicalEnvironment exterior.
El proceso consiste en dos pasos:
- Primero, cuando se crea una función f, no se crea en un espacio vacío. Hay un objeto LexicalEnvironment actual. En el caso anterior, es window (a está indefinido en el momento de la creación de la función).
Cuando se crea una función, obtiene una propiedad oculta llamada [[Scope]], que hace referencia al LexicalEnvironment actual.
Si se lee una variable, pero no se puede encontrar en ningún lugar, se genera un error.
Funciones anidadas
Las funciones pueden anidarse unas dentro de otras, formando una cadena de Entornos Léxicos que también puede llamarse cadena de alcance.
Entonces, la función g tiene acceso a g, a y f.
Clausuras
Una función anidada puede continuar existiendo después de que la función externa haya terminado:
Marcando Entornos Léxicos:
Como podemos ver, this.say es una propiedad en el objeto usuario, por lo que continúa existiendo después de que User haya finalizado.
Y si recuerdas, cuando se crea this.say, esta (como toda función) obtiene una referencia interna this.say.[[Scope]] al LexicalEnvironment actual. Así que, el LexicalEnvironment de la ejecución actual de User permanece en la memoria. Todas las variables de User también son sus propiedades, por lo que también se mantienen cuidadosamente, no se eliminan como usualmente.
Todo el objetivo es asegurar que si la función interna quiere acceder a una variable externa en el futuro, pueda hacerlo.
Para resumir:
- La función interna mantiene una referencia al LexicalEnvironment externo.
- La función interna puede acceder a variables de ella en cualquier momento, incluso si la función externa ha terminado.
- El navegador mantiene el EntornoLexical y todas sus propiedades (variables) en la memoria hasta que hay una función interna que lo referencia.
Esto se llama un cierre.
Respuesta #24
Las funciones de JavaScript pueden acceder a su:
- Argumentos
- Locales (es decir, sus variables locales y funciones locales)
Medio ambiente, que incluye:
- globales, incluido el DOM
- cualquier cosa en funciones externas
Si una función accede a su entorno, entonces la función es un cierre.
Tenga en cuenta que las funciones externas no son necesarias, aunque sí ofrecen beneficios que no discuto aquí. Al acceder a los datos en su entorno, una clausura mantiene esos datos vivos. En el subcaso de funciones externas/internas, una función externa puede crear datos locales y eventualmente salir, y aun así, si alguna función interna sobrevive después de que la función externa sale, entonces la(s) función(es) interna(s) mantienen vivos los datos locales de la función externa.
Ejemplo de un closure que utiliza el entorno global:
Imagina que los eventos de los botones de Voto a favor y Voto en contra de Stack Overflow se implementan como cierres, voteUp_click y voteDown_click, que tienen acceso a las variables externas isVotedUp e isVotedDown, que están definidas globalmente. (Para simplificar, me estoy refiriendo a los botones de Voto de Pregunta de StackOverflow, no al conjunto de botones de Voto de Respuesta.)
Cuando el usuario hace clic en el botón VoteUp, la función voteUp_click verifica si isVotedDown == true para determinar si votar a favor o simplemente cancelar un voto en contra. La función voteUp_click es un closure porque está accediendo a su entorno.
var isVotedUp = false;
var isVotedDown = false;
function voteUp_click() {
if (isVotedUp)
return;
else if (isVotedDown)
SetDownVote(false);
else
SetUpVote(true);
}
function voteDown_click() {
if (isVotedDown)
return;
else if (isVotedUp)
SetUpVote(false);
else
SetDownVote(true);
}
function SetUpVote(status) {
isVotedUp = status;
// Do some CSS stuff to Vote-Up button
}
function SetDownVote(status) {
isVotedDown = status;
// Do some CSS stuff to Vote-Down button
}
Las cuatro funciones son cierres ya que todas acceden a su entorno.
Respuesta #25
Como padre de un niño de 6 años, que actualmente enseña a niños pequeños (y un relativo novato en codificación sin educación formal, por lo que se requerirán correcciones), creo que la lección se asimilaría mejor a través del juego práctico. Si el niño de 6 años está listo para entender qué es un closure, entonces tiene la edad suficiente para intentarlo por sí mismo. Sugeriría pegar el código en jsfiddle.net, explicar un poco y dejarlos solos para que inventen una canción única. El texto explicativo a continuación probablemente sea más apropiado para un niño de 10 años.
function sing(person) {
var firstPart = "There was " + person + " who swallowed ";
var fly = function() {
var creature = "a fly";
var result = "Perhaps she'll die";
alert(firstPart + creature + "\n" + result);
};
var spider = function() {
var creature = "a spider";
var result = "that wiggled and jiggled and tickled inside her";
alert(firstPart + creature + "\n" + result);
};
var bird = function() {
var creature = "a bird";
var result = "How absurd!";
alert(firstPart + creature + "\n" + result);
};
var cat = function() {
var creature = "a cat";
var result = "Imagine That!";
alert(firstPart + creature + "\n" + result);
};
fly();
spider();
bird();
cat();
}
var person="an old lady";
sing(person);
INSTRUCCIONES
DATOS: Los datos son una colección de hechos. Pueden ser números, palabras, mediciones, observaciones o incluso solo descripciones de cosas. No se pueden tocar, oler ni saborear. Se pueden escribir, hablar y escuchar. Podrías usarlos para crear el tacto, el olor y el gusto usando una computadora. Pueden hacerse útiles mediante una computadora utilizando código.
CÓDIGO: Todo lo escrito arriba se llama código. Está escrito en JavaScript.
JAVASCRIPT: JavaScript es un lenguaje. Al igual que el inglés, el francés o el chino son idiomas. Hay muchos lenguajes que entienden las computadoras y otros procesadores electrónicos. Para que una computadora entienda JavaScript, necesita un intérprete. Imagínate si un profesor que sólo habla ruso viene a dar tu clase al colegio. Cuando el profesor dice "все садятся", la clase no entenderá. Pero, afortunadamente, tienes un estudiante ruso en tu clase que les dice a todos que esto significa "todos se sientan", y todos lo hacen. La clase es como una computadora y el estudiante ruso es el intérprete. Para JavaScript, el intérprete más común se llama navegador.
NAVEGADOR: Cuando te conectas a Internet en una computadora, tableta o teléfono para visitar un sitio web, utilizas un navegador. Ejemplos que puedes conocer son Internet Explorer, Chrome, Firefox y Safari. El navegador puede entender JavaScript y decirle a la computadora lo que necesita hacer. Las instrucciones de JavaScript se llaman funciones.
FUNCIÓN: Una función en JavaScript es como una fábrica. Puede ser una pequeña fábrica con solo una máquina dentro. O puede contener muchas otras pequeñas fábricas, cada una con muchas máquinas haciendo diferentes trabajos. En una fábrica de ropa de la vida real podrías tener rollos de tela y bobinas de hilo entrando y camisetas y jeans saliendo. Nuestra fábrica de JavaScript solo procesa datos, no puede coser, taladrar un agujero o fundir metal. En nuestra fábrica de JavaScript los datos entran y los datos salen.
Todo este asunto de los datos suena un poco aburrido, pero en realidad es muy interesante; podríamos tener una función que le diga a un robot qué preparar para la cena. Digamos que te invito a ti y a tu amigo a mi casa. A ti te gustan más los muslos de pollo, a mí me gustan las salchichas, tu amigo siempre quiere lo que tú quieres y mi amigo no come carne.
No tengo tiempo para ir de compras, así que la función necesita saber qué tenemos en la nevera para tomar decisiones. Cada ingrediente tiene un tiempo de cocción diferente y queremos que todo sea servido caliente por el robot al mismo tiempo. Necesitamos proporcionar a la función los datos sobre lo que nos gusta, la función podría 'hablar' con la nevera, y la función podría controlar el robot.
Una función normalmente tiene un nombre, paréntesis y llaves. Así:
function cookMeal() { /* STUFF INSIDE THE FUNCTION */ }
Nota que /*...*/ y // evitan que el navegador lea el código.
NOMBRE: Puedes llamar a una función casi con cualquier palabra que quieras. El ejemplo "cookMeal" es típico al unir dos palabras y darle a la segunda una letra mayúscula al principio, pero esto no es necesario. No puede tener un espacio, y no puede ser un número por sí solo.
PARÉNTESIS: "Paréntesis" o () son el buzón en la puerta de la fábrica de funciones de JavaScript o un buzón en la calle para enviar paquetes de información a la fábrica. A veces, el buzón podría estar marcado por ejemplo cookMeal(you, me, yourFriend, myFriend, fridge, dinnerTime), en cuyo caso sabes qué datos tienes que darle.
LLAVES: "Llaves" que se ven así {} son las ventanas tintadas de nuestra fábrica. Desde dentro de la fábrica se puede ver hacia afuera, pero desde afuera no se puede ver hacia adentro.
EL EJEMPLO DE CÓDIGO LARGO ANTERIOR
Nuestro código comienza con la palabra function, así que ¡sabemos que es una función! Luego el nombre de la función sing - esa es mi propia descripción de lo que trata la función. Luego paréntesis (). Los paréntesis siempre están ahí para una función. A veces están vacíos, y a veces tienen algo dentro. ¡Este tiene una palabra dentro: (person)! Después de esto hay una llave así {. Esto marca el inicio de la función sing(). Tiene un compañero que marca el final de sing() así }
function sing(person) { /* STUFF INSIDE THE FUNCTION */ }
Entonces, esta función podría tener algo que ver con cantar, y podría necesitar algunos datos sobre una persona. Tiene instrucciones dentro para hacer algo con esos datos.
Ahora, después de la función sing(), cerca del final del código está la línea
var person="an old lady";
VARIABLE: Las letras var significan "variable". Una variable es como un sobre. En el exterior de este sobre está marcado "persona". En el interior contiene un trozo de papel con la información que nuestra función necesita, algunas letras y espacios unidos como un pedazo de cuerda (se llama cadena) que forman una frase que dice "una anciana". Nuestro sobre podría contener otros tipos de cosas como números (llamados enteros), instrucciones (llamadas funciones), listas (llamadas arreglos). Debido a que esta variable está escrita fuera de todas las llaves {}, y porque puedes ver a través de las ventanas tintadas cuando estás dentro de las llaves, esta variable se puede ver desde cualquier lugar del código. Llamamos a esto una 'variable global'.
VARIABLE GLOBAL: person es una variable global, lo que significa que si cambias su valor de "una anciana" a "un joven", el person seguirá siendo un joven hasta que decidas cambiarlo de nuevo y que cualquier otra función en el código puede ver que es un joven. Presiona el botón F12 o mira la configuración de Opciones para abrir la consola de desarrollador de un navegador y escribe "person" para ver cuál es este valor. Escribe person="un joven" para cambiarlo y luego escribe "person" de nuevo para ver que ha cambiado.
Después de esto tenemos la línea
sing(person);
Esta línea está llamando a la función, como si estuviera llamando a un perro
"¡Vamos cantar, ven a buscar persona!"
Cuando el navegador haya cargado el código JavaScript y alcanzado esta línea, iniciará la función. Puse la línea al final para asegurarme de que el navegador tenga toda la información que necesita para ejecutarla.
Las funciones definen acciones - la función principal trata sobre cantar. Contiene una variable llamada firstPart que se aplica al canto sobre la persona que corresponde a cada uno de los versos de la canción: "Había " + persona + " que se tragó". Si escribes firstPart en la consola, no obtendrás una respuesta porque la variable está encerrada dentro de una función - el navegador no puede ver dentro de las ventanas tintadas de las llaves.
CIERRES: Los cierres son las funciones más pequeñas que están dentro de la gran función sing(). Las pequeñas fábricas dentro de la gran fábrica. Cada una tiene sus propias llaves, lo que significa que las variables dentro de ellas no se pueden ver desde afuera. Por eso, los nombres de las variables (creature y result) se pueden repetir en los cierres pero con diferentes valores. Si escribes estos nombres de variables en la ventana de la consola, no obtendrás su valor porque está oculto por dos capas de ventanas tintadas.
Los cierres todos saben cuál es la variable de la función sing() llamada firstPart, porque pueden ver desde sus ventanas tintadas.
Después de los cierres vienen las filas
fly();
spider();
bird();
cat();
La función sing() llamará a cada una de estas funciones en el orden en que se den. Luego, el trabajo de la función sing() habrá terminado.
Respuesta #26
Vale, hablando con un niño de 6 años, quizá usaría las siguientes asociaciones.
Imagina: estás jugando con tus hermanitos y hermanitas en toda la casa, y te estás moviendo con tus juguetes y llevaste algunos de ellos al cuarto de tu hermano mayor. Después de un rato, tu hermano regresó de la escuela y fue a su cuarto, y se encerró dentro, así que ahora ya no podías acceder a los juguetes que dejaste allí de manera directa. Pero podrías tocar la puerta y pedirle a tu hermano esos juguetes. Esto se llama closure de juguetes; tu hermano lo creó para ti, y ahora él está en un scope externo.
Compáralo con una situación en la que una puerta estaba cerrada por corriente de aire y nadie estaba dentro (ejecución general de la función), y luego ocurre un pequeño incendio local y quema la habitación (recolector de basura :D), y luego se construye una nueva habitación y ahora puedes dejar allí otros juguetes (nueva instancia de función), pero nunca recuperarás los mismos juguetes que se dejaron en la primera instancia de la habitación.
Para un niño avanzado pondría algo como lo siguiente. No es perfecto, pero te hace sentir acerca de lo que es:
function playingInBrothersRoom (withToys) {
// We closure toys which we played in the brother's room. When he come back and lock the door
// your brother is supposed to be into the outer [[scope]] object now. Thanks god you could communicate with him.
var closureToys = withToys || [],
returnToy, countIt, toy; // Just another closure helpers, for brother's inner use.
var brotherGivesToyBack = function (toy) {
// New request. There is not yet closureToys on brother's hand yet. Give him a time.
returnToy = null;
if (toy && closureToys.length > 0) { // If we ask for a specific toy, the brother is going to search for it.
for ( countIt = closureToys.length; countIt; countIt--) {
if (closureToys[countIt - 1] == toy) {
returnToy = 'Take your ' + closureToys.splice(countIt - 1, 1) + ', little boy!';
break;
}
}
returnToy = returnToy || 'Hey, I could not find any ' + toy + ' here. Look for it in another room.';
}
else if (closureToys.length > 0) { // Otherwise, just give back everything he has in the room.
returnToy = 'Behold! ' + closureToys.join(', ') + '.';
closureToys = [];
}
else {
returnToy = 'Hey, lil shrimp, I gave you everything!';
}
console.log(returnToy);
}
return brotherGivesToyBack;
}
// You are playing in the house, including the brother's room.
var toys = ['teddybear', 'car', 'jumpingrope'],
askBrotherForClosuredToy = playingInBrothersRoom(toys);
// The door is locked, and the brother came from the school. You could not cheat and take it out directly.
console.log(askBrotherForClosuredToy.closureToys); // Undefined
// But you could ask your brother politely, to give it back.
askBrotherForClosuredToy('teddybear'); // Hooray, here it is, teddybear
askBrotherForClosuredToy('ball'); // The brother would not be able to find it.
askBrotherForClosuredToy(); // The brother gives you all the rest
askBrotherForClosuredToy(); // Nothing left in there
Como puedes ver, los juguetes que quedaron en la habitación todavía son accesibles a través del hermano y no importa si la habitación está cerrada. Aquí hay un jsbin para experimentar con ello.
Respuesta #27
Una función en JavaScript no es solo una referencia a un conjunto de instrucciones (como en el lenguaje C), sino que también incluye una estructura de datos oculta que se compone de referencias a todas las variables no locales que utiliza (variables capturadas). A tales funciones de dos partes se les llaman closures. Cada función en JavaScript puede considerarse un closure.
Los closures son funciones con un estado. Es algo similar a "this" en el sentido de que "this" también proporciona estado para una función, pero la función y "this" son objetos separados ("this" es solo un parámetro elegante, y la única manera de enlazarlo permanentemente a una función es crear un closure). Mientras que "this" y la función siempre viven por separado, una función no puede separarse de su closure y el lenguaje no proporciona medios para acceder a las variables capturadas.
Debido a que todas estas variables externas referenciadas por una función anidada léxicamente son en realidad variables locales en la cadena de sus funciones léxicamente envolventes (se puede suponer que las variables globales son variables locales de alguna función raíz), y cada ejecución de una función crea nuevas instancias de sus variables locales, se deduce que cada ejecución de una función que devuelve (o transfiere de otro modo, como registrarla como una función de retorno) una función anidada crea un nuevo cierre (con su propio conjunto potencialmente único de variables no locales referenciadas que representan su contexto de ejecución).
Además, debe entenderse que las variables locales en JavaScript no se crean en el marco de pila, sino en el heap y se destruyen solo cuando nadie las está referenciando. Cuando una función retorna, las referencias a sus variables locales se disminuyen, pero aún pueden no ser nulas si durante la ejecución actual se convirtieron en parte de un cierre y todavía son referenciadas por sus funciones anidadas léxicamente (lo cual solo puede ocurrir si las referencias a estas funciones anidadas fueron devueltas o transferidas de otra manera a algún código externo).
Un ejemplo:
function foo (initValue) {
//This variable is not destroyed when the foo function exits.
//It is 'captured' by the two nested functions returned below.
var value = initValue;
//Note that the two returned functions are created right now.
//If the foo function is called again, it will return
//new functions referencing a different 'value' variable.
return {
getValue: function () { return value; },
setValue: function (newValue) { value = newValue; }
}
}
function bar () {
//foo sets its local variable 'value' to 5 and returns an object with
//two functions still referencing that local variable
var obj = foo(5);
//Extracting functions just to show that no 'this' is involved here
var getValue = obj.getValue;
var setValue = obj.setValue;
alert(getValue()); //Displays 5
setValue(10);
alert(getValue()); //Displays 10
//At this point getValue and setValue functions are destroyed
//(in reality they are destroyed at the next iteration of the garbage collector).
//The local variable 'value' in the foo is no longer referenced by
//anything and is destroyed too.
}
bar();
Respuesta #28
Una respuesta para un niño de seis años (suponiendo que sepa lo que es una función, lo que es una variable y lo que son los datos):
Las funciones pueden devolver datos. Un tipo de dato que puedes devolver desde una función es otra función. Cuando se devuelve esa nueva función, todas las variables y argumentos que se usaron en la función que la creó no desaparecen. En cambio, esa función principal "se cierra". En otras palabras, nadie puede mirar dentro de ella y ver las variables que utilizó, excepto la función que devolvió. Esa nueva función tiene una habilidad especial para mirar hacia atrás dentro de la función que la creó y ver los datos que hay dentro de ella.
function the_closure() {
var x = 4;
return function () {
return x; // Here, we look back inside the_closure for the value of x
}
}
var myFn = the_closure();
myFn(); //=> 4
Otra forma realmente simple de explicarlo es en términos de alcance:
Cada vez que creas un ámbito más pequeño dentro de un ámbito más grande, el ámbito más pequeño siempre podrá ver lo que hay en el ámbito más grande.
Respuesta #29
Quizás un poco más allá de todos excepto de los niños de seis años más precoz, pero algunos ejemplos que me ayudaron a que el concepto de closure en JavaScript tuviera sentido para mí.
Un closure es una función que tiene acceso al ámbito de otra función (sus variables y funciones). La manera más fácil de crear un closure es con una función dentro de otra función; la razón es que en JavaScript una función siempre tiene acceso al ámbito de la función que la contiene.
function outerFunction() {
var outerVar = "monkey";
function innerFunction() {
alert(outerVar);
}
innerFunction();
}
outerFunction();ALERTA: mono
En el ejemplo anterior, se llama a outerFunction, que a su vez llama a innerFunction. Observa cómo outerVar está disponible para innerFunction, evidenciado por su correcta alerta del valor de outerVar.
Ahora considera lo siguiente:
function outerFunction() {
var outerVar = "monkey";
function innerFunction() {
return outerVar;
}
return innerFunction;
}
var referenceToInnerFunction = outerFunction();
alert(referenceToInnerFunction());ALERTA: mono
referenceToInnerFunction se establece en outerFunction(), que simplemente devuelve una referencia a innerFunction. Cuando se llama a referenceToInnerFunction, devuelve outerVar. Nuevamente, como se indicó anteriormente, esto demuestra que innerFunction tiene acceso a outerVar, una variable de outerFunction. Además, es interesante notar que mantiene este acceso incluso después de que outerFunction ha terminado de ejecutarse.
Y aquí es donde las cosas se ponen realmente interesantes. Si nos deshiciéramos de outerFunction, digamos que la pusiéramos en null, podrías pensar que referenceToInnerFunction perdería su acceso al valor de outerVar. Pero este no es el caso.
function outerFunction() {
var outerVar = "monkey";
function innerFunction() {
return outerVar;
}
return innerFunction;
}
var referenceToInnerFunction = outerFunction();
alert(referenceToInnerFunction());
outerFunction = null;
alert(referenceToInnerFunction());ALERTA: mono ALERTA: mono
¿Pero cómo es esto posible? ¿Cómo puede referenceToInnerFunction seguir conociendo el valor de outerVar ahora que outerFunction ha sido establecido a null?
La razón por la que referenceToInnerFunction aún puede acceder al valor de outerVar es porque cuando la closure se creó por primera vez colocando innerFunction dentro de outerFunction, innerFunction agregó una referencia al ámbito de outerFunction (sus variables y funciones) a su cadena de ámbito. Lo que esto significa es que innerFunction tiene un puntero o referencia a todas las variables de outerFunction, incluyendo outerVar. Entonces, incluso cuando outerFunction ha terminado de ejecutarse, o incluso si se elimina o se establece en null, las variables en su ámbito, como outerVar, permanecen en la memoria debido a la referencia pendiente hacia ellas por parte de la innerFunction que ha sido devuelta a referenceToInnerFunction. Para liberar realmente outerVar y el resto de las variables de outerFunction de la memoria, se tendría que eliminar esta referencia pendiente hacia ellas, por ejemplo, estableciendo también referenceToInnerFunction en null.
//////////
Otras dos cosas a notar sobre los closures. Primero, el closure siempre tendrá acceso a los últimos valores de su función contenedora.
function outerFunction() {
var outerVar = "monkey";
function innerFunction() {
alert(outerVar);
}
outerVar = "gorilla";
innerFunction();
}
outerFunction();ALERTA: gorila
En segundo lugar, cuando se crea un cierre, este retiene una referencia a todas las variables y funciones de la función que lo contiene; no puede escoger y elegir. Por lo tanto, los cierres deben usarse con moderación, o al menos con cuidado, ya que pueden consumir mucha memoria; muchas variables pueden mantenerse en memoria mucho después de que la función contenedora haya terminado de ejecutarse.
Respuesta #30
Simplemente los remitiría a la página de Cierres de Mozilla. Es la mejor y más concisa y simple explicación de los conceptos básicos y el uso práctico de los cierres que he encontrado. Se recomienda encarecidamente a cualquiera que esté aprendiendo JavaScript.
Y sí, incluso lo recomendaría a un niño de 6 años, si el niño de 6 años está aprendiendo sobre closures, entonces es lógico que esté listo para comprender la explicación concisa y simple proporcionada en el artículo.







