javascript - programacion - que es closure en español
¿Cómo funcionan los cierres de JavaScript? (30)
¿Cómo explicaría los cierres de JavaScript a alguien con un conocimiento de los conceptos en que consisten (por ejemplo, funciones, variables y similares), pero no entiende los cierres por sí mismos?
He visto el ejemplo del Esquema dado en Wikipedia, pero desafortunadamente no ayudó.
Los niños siempre recordarán los secretos que han compartido con sus padres, incluso después de que estos se hayan ido. Esto es lo que son los cierres para funciones.
Los secretos para las funciones de JavaScript son las variables privadas.
var parent = function() {
var name = "Mary"; // secret
}
Cada vez que lo llamas, la variable local "nombre" se crea y se le da el nombre "María". Y cada vez que la función sale, la variable se pierde y el nombre se olvida.
Como puede suponer, porque 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 en realidad no importa. Sabemos que están ahí, en algún lugar, escondidos en la memoria.
Pero, en JavaScript hay una cosa muy especial que las funciones que se crean dentro de otras funciones, también pueden conocer las variables locales de sus padres y mantenerlas mientras vivan.
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 principal, puede crear una o más funciones secundarias que compartan las variables secretas desde el lugar secreto.
Pero lo triste es que si el niño es también una variable privada de su función principal, también morirá cuando el padre termine, y los secretos morirán con ellos.
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 corre", su recuerdo no se pierde y su hija siempre recordará su nombre y otros secretos que compartieron durante su tiempo juntos.
Entonces, si llamas a la niña "Alice", ella responderá.
child("Alice") => "My name is Alice, child of Mary"
Eso es todo lo que hay que contar.
Cierres de JavaScript para principiantes
Enviado por Morris el martes, 2006-02-21 10:19. Editado por la comunidad desde.
Los cierres no son mágicos
Esta página explica los cierres para que un programador pueda entenderlos, utilizando el código JavaScript de trabajo. No es para gurús o programadores funcionales.
Los cierres no son difíciles de entender una vez que el concepto de núcleo se ha desarrollado. Sin embargo, ¡son imposibles de entender leyendo cualquier explicación teórica o académicamente orientada!
Este artículo está dirigido a programadores con cierta experiencia en programación en un lenguaje general y que pueden leer la siguiente función de JavaScript:
function sayHello(name) {
var text = ''Hello '' + name;
var say = function() { console.log(text); }
say();
}
sayHello(''Joe'');
Dos breves resúmenes
Cuando una función (foo) declara otras funciones (barra y baz), la familia de variables locales creadas en foo no se destruye cuando la función sale. Las variables simplemente se vuelven invisibles para el mundo exterior. Por lo tanto, Foo puede devolver astutamente las funciones bar y baz, y pueden seguir leyendo, escribiendo y comunicándose entre sí a través de esta familia cerrada de variables ("el cierre") con la que nadie más puede entrometerse, ni siquiera alguien que llama. Foo otra vez en el futuro.
Un cierre es una forma de apoyar funciones de primera clase ; es una expresión que puede hacer referencia a variables dentro de su alcance (cuando se declaró por primera vez), asignarse a una variable, pasarse como argumento a una función o devolverse como resultado de una función.
Un ejemplo de un cierre.
El siguiente código devuelve una referencia a una función:
function sayHello2(name) {
var text = ''Hello '' + name; // Local variable
var say = function() { console.log(text); }
return say;
}
var say2 = sayHello2(''Bob'');
say2(); // logs "Hello Bob"
La mayoría de los programadores de JavaScript entenderán cómo una referencia a una función se devuelve a una variable (por say2
) en el código anterior. Si no lo hace, entonces debe mirar eso antes de que pueda aprender acerca de los cierres. Un programador que usa C pensaría que la función devolvía un puntero a una función, y que las variables say
y say2
eran un puntero a una función.
Hay una diferencia crítica entre un puntero C a una función y una referencia de JavaScript a una función. En JavaScript, puede pensar que una variable de referencia de función tiene tanto un puntero a una función como un puntero oculto a un cierre.
El código anterior tiene un cierre porque la función anónima function() { console.log(text); }
function() { console.log(text); }
se declara dentro de otra función, sayHello2()
en este ejemplo. En JavaScript, si utiliza la palabra clave de function
dentro de otra función, está creando un cierre.
En C y en la mayoría de los otros lenguajes comunes, después de que una función regresa, todas las variables locales ya no son accesibles porque se destruye el marco de pila.
En JavaScript, si declara una función dentro de otra función, las variables locales de la función externa pueden permanecer accesibles después de regresar de ella. Esto se demuestra anteriormente, porque llamamos a la función say2()
después de haber regresado de sayHello2()
. Observe que el código que llamamos hace referencia al text
variable, que era una variable local de la función sayHello2()
.
function() { console.log(text); } // Output of say2.toString();
En cuanto a la salida de say2.toString()
, podemos ver que el código se refiere al text
variable. La función anónima puede hacer referencia al text
que contiene el valor ''Hello Bob''
porque las variables locales de sayHello2()
se han mantenido vivas en secreto en un cierre.
El genio es que en JavaScript una referencia de función también tiene una referencia secreta al cierre en el que se creó, de manera similar a como los delegados son un puntero a un método más una referencia secreta a un objeto.
Más ejemplos
Por alguna razón, los cierres parecen ser muy difíciles de entender cuando lees sobre ellos, pero cuando ves algunos ejemplos, queda claro cómo funcionan (me tomó un tiempo). Recomiendo trabajar con los ejemplos cuidadosamente hasta que entiendas cómo funcionan. Si comienzas a usar cierres sin entender completamente cómo funcionan, ¡pronto crearías algunos errores muy extraños!
Ejemplo 3
Este ejemplo muestra que las variables locales no se copian, se mantienen por referencia. ¡Es como si el marco de pila se mantuviera vivo en la memoria incluso después de que existe la función externa!
function say667() {
// Local variable that ends up within closure
var num = 42;
var say = function() { console.log(num); }
num++;
return say;
}
var sayNumber = say667();
sayNumber(); // logs 43
Ejemplo 4
Las tres funciones globales tienen una referencia común al mismo cierre porque todas se declaran dentro de una sola llamada a setupSomeGlobals()
.
var gLogNumber, gIncreaseNumber, gSetNumber;
function setupSomeGlobals() {
// Local variable that ends up within closure
var num = 42;
// Store some references to functions as global variables
gLogNumber = function() { console.log(num); }
gIncreaseNumber = function() { num++; }
gSetNumber = function(x) { num = x; }
}
setupSomeGlobals();
gIncreaseNumber();
gLogNumber(); // 43
gSetNumber(5);
gLogNumber(); // 5
var oldLog = gLogNumber;
setupSomeGlobals();
gLogNumber(); // 42
oldLog() // 5
Las tres funciones tienen acceso compartido al mismo cierre: las variables locales de setupSomeGlobals()
cuando se definieron las tres funciones.
Tenga en cuenta que en el ejemplo anterior, si llama a setupSomeGlobals()
nuevamente, se setupSomeGlobals()
un nuevo cierre (stack-frame!). Las antiguas gLogNumber
, gIncreaseNumber
, gSetNumber
se sobrescriben con nuevas funciones que tienen el nuevo cierre. (En JavaScript, cada vez que declara una función dentro de otra función, las funciones internas se recrean nuevamente cada vez que se llama a la función externa).
Ejemplo 5
Este ejemplo muestra que el cierre contiene variables locales que se declararon dentro de la función externa antes de salir. Tenga en cuenta que la variable alice
se declara realmente después de la función anónima. La función anónima se declara primero, y cuando se llama a esa función, puede acceder a la variable alice
porque alice
está en el mismo ámbito (JavaScript hace la elevación de variables ). También sayAlice()()
simplemente llama directamente a la referencia de función devuelta por sayAlice()
- es exactamente lo mismo que se hizo anteriormente pero sin la variable temporal.
function sayAlice() {
var say = function() { console.log(alice); }
// Local variable that ends up within closure
var alice = ''Hello Alice'';
return say;
}
sayAlice()();// logs "Hello Alice"
Difícil: también tenga en cuenta que la variable say
también está dentro del cierre, y puede accederse a ella mediante cualquier otra función que pueda declararse dentro de sayAlice()
, o puede accederse recursivamente dentro de la función inside.
Ejemplo 6
Este es un verdadero problema para muchas personas, así que necesitas entenderlo. Tenga mucho cuidado si está definiendo una función dentro de un bucle: es posible que las variables locales del cierre no actúen como podría pensar.
Debe comprender la función de "elevación variable" en Javascript para comprender este ejemplo.
function buildList(list) {
var result = [];
for (var i = 0; i < list.length; i++) {
var item = ''item'' + i;
result.push( function() {console.log(item + '' '' + list[i])} );
}
return result;
}
function testList() {
var fnlist = buildList([1,2,3]);
// Using j only to help prevent confusion -- could use i.
for (var j = 0; j < fnlist.length; j++) {
fnlist[j]();
}
}
testList() //logs "item2 undefined" 3 times
La línea result.push( function() {console.log(item + '' '' + list[i])}
agrega una referencia a una función anónima tres veces en la matriz de resultados. Si no está tan familiarizado con las funciones anónimas, piense en es como
pointer = function() {console.log(item + '' '' + list[i])};
result.push(pointer);
Tenga en cuenta que cuando ejecuta el ejemplo, ¡ "item2 undefined"
se registra tres veces! Esto se debe a que, al igual que en los ejemplos anteriores, solo hay un cierre para las variables locales para buildList
(que son result
, i
y item
). Cuando las funciones anónimas se llaman en la línea fnlist[j]()
; todos usan el mismo cierre único, y usan el valor actual para i
y el item
dentro de ese cierre (donde i
tiene un valor de 3
porque el bucle se había completado, y el item
tiene un valor de ''item2''
). Tenga en cuenta que estamos indexando desde 0, por lo que el item
tiene un valor de item2
. Y el i ++ incrementará i
al valor 3
.
Puede ser útil ver qué sucede cuando se utiliza una declaración a nivel de bloque del item
variable (a través de la palabra clave let
) en lugar de una declaración de variable con ámbito de función a través de la palabra clave var
. Si se realiza ese cambio, entonces cada función anónima en el result
la matriz tiene su propio cierre; cuando se ejecuta el ejemplo, la salida es la siguiente:
item0 undefined
item1 undefined
item2 undefined
Si la variable i
también se define usando let
lugar de var
, entonces la salida es:
item0 1
item1 2
item2 3
Ejemplo 7
En este ejemplo final, cada llamada a la función principal crea un cierre separado.
function newClosure(someNum, someRef) {
// Local variables that end up within closure
var num = someNum;
var anArray = [1,2,3];
var ref = someRef;
return function(x) {
num += x;
anArray.push(num);
console.log(''num: '' + num +
''; anArray: '' + anArray.toString() +
''; ref.someVar: '' + ref.someVar + '';'');
}
}
obj = {someVar: 4};
fn1 = newClosure(4, obj);
fn2 = newClosure(5, obj);
fn1(1); // num: 5; anArray: 1,2,3,5; ref.someVar: 4;
fn2(1); // num: 6; anArray: 1,2,3,6; ref.someVar: 4;
obj.someVar++;
fn1(2); // num: 7; anArray: 1,2,3,5,7; ref.someVar: 5;
fn2(2); // num: 8; anArray: 1,2,3,6,8; ref.someVar: 5;
Resumen
Si todo parece completamente incierto, entonces lo mejor es jugar con los ejemplos. Leer una explicación es mucho más difícil que entender ejemplos. Mis explicaciones de cierres y apilamientos, etc. no son técnicamente correctas, son simplificaciones generales que pretenden ayudar a comprender. Una vez que la idea básica es asimilada, puedes recoger los detalles más adelante.
Puntos finales:
- Cuando se utiliza la
function
dentro de otra función, se utiliza un cierre. - Siempre que use
eval()
dentro de una función, se usa un cierre. El texto queeval
puede hacer referencia a las variables locales de la función, y dentro deeval
incluso puede crear nuevas variables locales utilizandoeval(''var foo = …'')
- Cuando utiliza la
new Function(…)
(el constructor de funciones ) dentro de una función, no crea un cierre. (La nueva función no puede hacer referencia a las variables locales de la función externa). - Un cierre en JavaScript es como mantener una copia de todas las variables locales, tal como eran cuando una función salía.
- Probablemente es mejor pensar que un cierre siempre se crea solo una entrada a una función, y las variables locales se agregan a ese cierre.
- Se mantiene un nuevo conjunto de variables locales cada vez que se llama a una función con un cierre (dado que la función contiene una declaración de función dentro de ella, y se devuelve una referencia a esa función interna o se mantiene una referencia externa de alguna manera) ).
- Dos funciones pueden parecer que tienen el mismo texto de origen, pero tienen un comportamiento completamente diferente debido a su cierre "oculto". No creo que el código JavaScript pueda realmente averiguar si una referencia de función tiene un cierre o no.
- Si está intentando realizar modificaciones dinámicas en el código fuente (por ejemplo:
myFunction = Function(myFunction.toString().replace(/Hello/,''Hola''));
), no funcionará simyFunction
es un cierre ( por supuesto, nunca se le ocurriría hacer una sustitución de cadena de código fuente en tiempo de ejecución, pero ...). - Es posible obtener declaraciones de funciones dentro de las declaraciones de funciones dentro de las funciones y mdash, y puede obtener cierres en más de un nivel.
- Creo que normalmente un cierre es un término para la función junto con las variables que se capturan. Tenga en cuenta que no uso esa definición en este artículo!
- Sospecho que los cierres en JavaScript difieren de los que se encuentran normalmente en los lenguajes funcionales.
Campo de golf
- Atributos privados simulados de Douglas Crockford y métodos privados para un objeto, utilizando cierres.
- Una gran explicación de cómo los cierres pueden causar pérdidas de memoria en IE si no tiene cuidado.
Gracias
Si acaba de aprender acerca de los cierres (¡aquí o en otro lugar!), Entonces me interesa cualquier comentario de usted sobre cualquier cambio que pueda sugerir que pueda aclarar este artículo. Envíe un correo electrónico a morrisjohns.com (morris_closure @). Tenga en cuenta que no soy un gurú en JavaScript, ni en cierres.
La publicación original de Morris se puede encontrar en el Archivo de Internet .
El hombre de paja
Necesito saber cuántas veces se ha hecho clic en un botón y hacer algo con 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 se adentra en el ámbito externo al agregar una variable, cuyo único propósito es realizar un seguimiento del conteo. En algunas situaciones, esto sería preferible ya que su 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 incluir esta funcionalidad dentro del controlador 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>
Note 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 se creó, indefinidamente. Para aplicar esto de manera práctica, invoco de inmediato una función que devuelve otra función, y como la función que estoy devolviendo tiene acceso a la variable de conteo interna (debido al comportamiento de cierre explicado anteriormente), esto se traduce en un ámbito privado para el uso por parte de función ... no tan simple? Vamos a diluirlo ...
Un simple cierre de una 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 disponibles directamente para el objeto de función devuelta ...
func(); // Alerts "val"
func.a; // Undefined
¿Consíguelo? Entonces, en nuestro ejemplo principal, la variable de conteo está contenida dentro del cierre y siempre está disponible para el controlador de eventos, por lo que conserva su estado de clic a clic.
Además, este estado de variable privada es totalmente accesible, tanto para las lecturas como para la asignación a sus variables de ámbito privado.
Hay que ir ahora estás encapsulando completamente este comportamiento.
Publicación completa del blog (incluidas las consideraciones de jQuery)
¿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);
* AC # pregunta
Los cierres son simples:
El siguiente ejemplo simple cubre todos los puntos principales de los cierres de 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 continúa siendo utilizable por esa calculadora add
y multiply
funciona mucho después de las make_calculator
devoluciones.
Si está familiarizado con los cuadros de pila, estas calculadoras parecen extrañas: ¿Cómo pueden seguir accediendo n
después de las make_calculator
devoluciones? La respuesta es imaginar que JavaScript no usa "marcos de pila", sino que utiliza "marcos de pila", que pueden persistir después de la llamada a la función que los generó.
Las funciones internas como add
y multiply
, que las variables de acceso declaradas en una función externa ** , se denominan cierres .
Eso es prácticamente todo lo que hay para los cierres.
* Por ejemplo, cubre todos los puntos en el artículo "Cierres para tontos" que se encuentra en otra respuesta , excepto en el ejemplo 6, que simplemente muestra que las variables se pueden usar antes de que se declaren, un hecho que hay que saber pero que no tiene relación alguna con los cierres. También cubre todos los puntos en la respuesta aceptada , excepto los puntos (1) que las funciones copian sus argumentos en las variables locales (los argumentos de la función nombrada) y (2) que copiar números crea un nuevo número, pero copiando una referencia de objeto Te da otra referencia al mismo objeto. Estos también son buenos para saber, pero nuevamente no están relacionados con los cierres. También es muy similar al ejemplo en esta respuesta pero un poco más corto y menos abstracto. No cubre el punto deEsta respuesta o este comentario , que es que JavaScript dificulta la conexión de la corrienteel valor de una variable de bucle en su función interna: el paso de "conexión" solo se puede realizar con una función auxiliar que encierre su función interna y se invoque en cada iteración de bucle. (Estrictamente hablando, la función interna accede a la copia de la variable de la función auxiliar, en lugar de tener algo conectado). Una vez más, es muy útil al crear cierres, pero no es parte de lo que es un cierre o cómo funciona. Existe una confusión adicional debido a que los cierres funcionan de manera diferente en lenguajes funcionales como ML, donde las variables están vinculadas a los valores en lugar de al espacio de almacenamiento, lo que proporciona un flujo constante de personas que entienden los cierres de una manera (es decir, la forma de "conexión"). Simplemente incorrecto para JavaScript, donde las variables siempre están vinculadas al espacio de almacenamiento y nunca a los valores.
** Cualquier función externa, si hay varias anidadas, o incluso en el contexto global, como esta respuesta apunta claramente.
Cuando vea la palabra clave de función dentro de otra función, la función interna tendrá acceso a las variables en la función externa.
function foo(x) {
var tmp = 3;
function bar(y) {
console.log(x + y + (++tmp)); // will log 16
}
bar(10);
}
foo(2);
Esto siempre registrará 16, porque la bar
puede acceder a la x
que se definió como un argumento para foo
, y también puede acceder a tmp
desde foo
.
Eso es un cierre. Una función no tiene que volver para que se llame cierre. Simplemente accediendo a variables fuera de su alcance léxico inmediato crea 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 is now a closure.
bar(10);
La función anterior también registrará 16, porque la bar
todavía puede referirse a x
y tmp
, aunque ya no esté directamente dentro del alcance.
Sin embargo, dado que tmp
todavía está dando vueltas dentro del cierre de la bar
, también se está incrementando. Se incrementará cada vez que llames 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. Junto con los argumentos de la función y el objeto principal, este contexto de ejecución también recibe todas las variables declaradas fuera de él (en el ejemplo anterior, tanto ''a'' como ''b'').
Es posible crear más de una función de cierre, ya sea devolviendo una lista de ellas o configurándolas en variables globales. Todos estos se referirán al mismo x
y al mismo tmp
, no hacen sus propias copias.
Aquí el número x
es un número literal. Al igual que con otros literales en JavaScript, cuando se llama foo
, el número x
se copia en foo
como su argumento x
.
Por otro lado, JavaScript siempre usa referencias cuando se trata de objetos. Si, por ejemplo, foo
a foo
con un objeto, el cierre que devuelve hará referencia al objeto original.
function foo(x) {
var tmp = 3;
return function (y) {
console.log(x + y + tmp);
x.memb = x.memb ? x.memb + 1 : 1;
console.log(x.memb);
}
}
var age = new Number(2);
var bar = foo(age); // bar is now a closure referencing age.
bar(10);
Como se esperaba, cada llamada a la bar(10)
incrementará x.memb
. Lo que podría no esperarse es que x
simplemente se refiere al mismo objeto que la variable de age
. Después de un par de llamadas a la bar
, age.memb
será 2! Esta referencia es la base para las fugas de memoria con objetos HTML.
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 de encerramiento no necesita regresar para poder crear su cierre. En su lugar, puede asignar su función interna a una variable en un ámbito externo, o pasarla como un argumento a otra función a la que se pueda llamar de inmediato o en cualquier momento posterior.Por lo tanto, el cierre de la función de encierro probablemente se crea tan pronto como se llama a la función de encierro, ya que cualquier función interna tiene acceso a ese cierre cada vez que se llama a la función interna, antes o después de que la función de encierre retorne.
- Un cierre no hace referencia a una copia de los valores antiguos de las variables en su alcance. Las variables en sí mismas son parte del cierre, por lo que el valor visto al acceder a una de esas variables es el último valor en el momento en que se accede. Esta es la razón por la que las funciones internas creadas dentro de los 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 llama la función.
- Las "variables" en un cierre incluyen cualquier función nombrada declarada dentro de la función. También incluyen argumentos de la función. Un cierre también tiene acceso a sus variables de cierre de cierre, hasta el alcance global.
- Los cierres utilizan la memoria, pero no causan pérdidas de memoria ya que JavaScript por sí solo limpia sus propias estructuras circulares a las que no se hace referencia. Las fugas de memoria de Internet Explorer que involucran cierres se crean cuando falla al desconectar los valores de los atributos DOM que hacen referencia a los cierres, manteniendo así las referencias a estructuras posiblemente circulares.
Los cierres son difíciles de explicar porque se utilizan para hacer que funcione un poco de comportamiento que, de manera intuitiva, todos esperan que funcione. Encuentro la mejor manera de explicarlos (y la forma en que aprendí lo que hacen) es imaginar la situación sin ellos:
var bind = function(x) {
return function(y) { return x + y; };
}
var plus5 = bind(5);
console.log(plus5(3));
¿Qué pasaría aquí si JavaScript no conociera los cierres? Simplemente reemplace la llamada en la última línea por su cuerpo de método (que es básicamente lo que hacen las llamadas de función) y obtendrá:
console.log(x + 3);
Ahora, ¿dónde está la definición de x
? No lo definimos en el ámbito actual. La única solución es dejar que plus5
lleve su alcance (o, más bien, el alcance de su padre). De esta manera, x
está bien definido y está vinculado al valor 5.
PRÓLOGO: esta respuesta fue escrita cuando la pregunta era:
Como dijo el viejo Albert: "Si no puedes explicárselo a un niño de seis años, realmente no lo entiendes". Bueno, intenté explicarle los cierres de JS a un amigo de 27 años y fracasé por completo.
¿Alguien puede considerar que tengo 6 años y que estoy extrañamente interesado en ese tema?
Estoy bastante seguro de que fui una de las únicas personas que intentaron 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. Esperemos que la idea general de la historia siga siendo divertida para algunos.
Soy un gran fan de la analogía y la metáfora cuando explico conceptos difíciles, así que déjame probar mi historia con una historia.
Había una vez:
Había una princesa ...
function princess() {
Vivía en un mundo maravilloso lleno de aventuras. Conoció a su Príncipe Azul, recorrió su mundo en un unicornio, luchó contra dragones, se encontró con animales que hablaban y muchas otras cosas fantásticas.
var adventures = [];
function princeCharming() { /* ... */ }
var unicorn = { /* ... */ },
dragons = [ /* ... */ ],
squirrel = "Hello!";
/* ... */
Pero siempre tendría que volver a su aburrido mundo de tareas y adultos.
return {
Y a menudo les contaba su última aventura increíble como princesa.
story: function() {
return adventures[adventures.length - 1];
}
};
}
Pero todo lo que verían es una niña ...
var littleGirl = princess();
... contando historias sobre magia y fantasía.
littleGirl.story();
Y a pesar de que los adultos sabían de princesas reales, nunca creerían en los unicornios o dragones porque nunca podrían verlos. Los adultos dijeron 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 dentro.
Tomando en serio la pregunta, deberíamos descubrir de qué forma es capaz cognitivamente un niño típico de 6 años, aunque hay que reconocer que uno que está interesado en JavaScript no es tan típico.
Sobre Desarrollo Infantil: 5 a 7 años dice:
Su hijo podrá seguir instrucciones de dos pasos. Por ejemplo, si le dice a su hijo: "Ve a la cocina y tráeme una bolsa de basura" podrán recordar esa dirección.
Podemos usar este ejemplo para explicar los cierres, como sigue:
La cocina es un cierre que tiene una variable local, llamada
trashBags
. Hay una función dentro de la cocina llamadagetTrashBag
que obtiene una bolsa de basura y la devuelve.
Podemos codificar esto en JavaScript de esta manera:
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 A
Otros puntos que explican por qué los cierres son interesantes:
- Cada vez que se llama a
makeKitchen()
, se crea un nuevo cierre con sus propiastrashBags
separadas. - La variable
trashBags
es local en el interior de cada cocina y no es accesible desde el exterior, pero la función interna en la propiedadgetTrashBag
sí tiene acceso a ella. - Cada llamada de función crea un cierre, pero no habría necesidad de mantener el cierre alrededor a menos que una función interna, que tiene acceso al interior del cierre, pueda ser llamada desde fuera del cierre. Devolver el objeto con la función
getTrashBag
hace aquí.
En informática, 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 , cada función es un cierre . Siempre tiene acceso a las variables definidas en el ámbito que lo rodea.
Dado que la construcción de definición de alcance en JavaScript es una función , no un bloque de código como en muchos otros idiomas, lo que generalmente entendemos por cierre en JavaScript es una función que trabaja con variables no locales definidas en la función circundante ya ejecutada .
Los cierres a menudo se usan para crear funciones con algunos datos privados ocultos (pero no siempre es así).
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 serlo. Puede ser nombrado (por ejemplo mkdb
) y ejecutarse 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 cierres es cuando no devolvemos una función, sino un objeto que contiene múltiples funciones para diferentes propósitos, cada una de las cuales tiene acceso a los mismos datos.
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 cierres son un truco de diseño complejo para abordar problemas específicos. No, los cierres se tratan simplemente de usar una variable que proviene de un alcance superior desde la perspectiva de dónde se declaró la función (no se ejecutó) .
Ahora lo que te permite hacer puede ser más espectacular, ver otras respuestas.
Bien, hablando con un niño de 6 años, posiblemente usaría las siguientes asociaciones.
Imagínate: estás jugando con tus hermanos pequeños en toda la casa, te estás moviendo con tus juguetes y llevándolos a la habitación de tu hermano mayor. Al cabo de un rato, tu hermano regresó de la escuela y fue a su habitación, y él lo encerró dentro, así que ahora ya no podrías acceder a los juguetes que quedaban allí de forma directa. Pero podrías golpear la puerta y pedirle a tu hermano esos juguetes. Esto se llama cierre de juguete ; tu hermano lo inventó para ti, y ahora está en el ámbito externo .
Compare con una situación en la que una puerta estaba cerrada con llave y nadie adentro (ejecución de la función general), luego ocurre un incendio local que quema la habitación (recolector de basura: D), y luego se construyó una nueva habitación y ahora puede irse hay otros juguetes allí (nueva instancia de función), pero nunca se obtienen los mismos juguetes que quedaron en la primera instancia de la sala.
Para un niño avanzado pondría algo como lo siguiente. No es perfecto, pero te hace sentir 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 puede ver, los juguetes que quedan en la habitación aún son accesibles a través del hermano y no importa si la habitación está cerrada con llave. Aquí hay un jsbin para jugar con él.
Cómo se lo explicaría a un niño de seis años:
¿Sabes cómo los adultos pueden ser dueños de una casa y la llaman su hogar? Cuando una madre tiene un hijo, el niño realmente no posee nada, ¿verdad? Pero los padres son dueños de una casa, por lo que cada vez que alguien le pregunta al niño "¿Dónde está tu casa?", Él / ella 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) poder decir que tiene un hogar, aunque en realidad los padres son los dueños de la casa.
Ejemplo para el primer punto por dlaliberte:
Un cierre no solo se crea cuando devuelves una función interna. De hecho, la función de encerramiento no necesita regresar en absoluto. En su lugar, puede asignar su función interna a una variable en un ámbito externo, o pasarla como un argumento a otra función en la que podría usarse de inmediato. Por lo tanto, el cierre de la función de encierro probablemente ya existe en el momento en que se llamó a la función de encierro, ya que cualquier función interna tiene acceso a ella 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);
Las funciones de JavaScript pueden acceder a su:
- Argumentos
- Locales (es decir, sus variables locales y funciones locales)
- Medio ambiente, que incluye:
- Globales, incluyendo 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 menciono aquí. Al acceder a los datos en su entorno, un cierre mantiene esos datos vivos. En el subcaso de funciones externas / internas, una función externa puede crear datos locales y eventualmente salir, y sin embargo, si alguna función interna sobrevive después de que la función externa finaliza, las funciones internas mantienen la información local de la función externa viva.
Ejemplo de un cierre que utiliza el entorno global:
Imagine que los eventos del botón de desbordamiento de pila y de botón de votación se implementan como cierres, voteUp_click y voteDown_click, que tienen acceso a variables externas isVotedUp e isVotedDown, que se definen globalmente. (En aras de la simplicidad, me refiero a los botones de votación de preguntas de , no a la matriz de botones de respuesta de votos).
Cuando el usuario hace clic en el botón Votar, la función voteUp_click verifica si isVotedDown == true para determinar si se debe votar o simplemente cancelar una votación negativa. La función voteUp_click es un cierre 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 de estas funciones son cierres, ya que todas acceden a su entorno.
Sé que ya hay muchas soluciones, pero creo que este pequeño y simple script 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
Tal vez un poco más allá de todos, pero el más precoz de los niños de seis años, pero algunos ejemplos que ayudaron a hacer que el concepto de cierre en JavaScript fuera un clic para mí.
Un cierre es una función que tiene acceso al alcance de otra función (sus variables y funciones). La forma más fácil de crear un cierre es con una función dentro de una función; La razón es que en JavaScript una función siempre tiene acceso al alcance de su función contenedora.
function outerFunction() {
var outerVar = "monkey";
function innerFunction() {
alert(outerVar);
}
innerFunction();
}
outerFunction();
ALERTA: mono
En el ejemplo anterior, se llama outerFunction que, a su vez, llama a innerFunction. Tenga en cuenta cómo es una función de la barra externa disponible para la función interna, como lo demuestra su alerta correcta sobre el valor de la barra externa.
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 observar que conserva este acceso incluso después de que outerFunction haya terminado de ejecutarse.
Y aquí es donde las cosas se ponen realmente interesantes. Si tuviéramos que deshacernos de outerFunction, supongamos que sea nulo, podría 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? ¿Cómo puede referenceToInnerFunction saber el valor de outerVar ahora que outerFunction se ha establecido en nulo?
La razón por la que referenceToInnerFunction aún puede acceder al valor de outerVar se debe a que cuando el cierre se creó por primera vez colocando innerFunction dentro de outerFunction, innerFunction agregó una referencia al alcance de outerFunction (sus variables y funciones) a su cadena de alcance. Lo que esto significa es que innerFunction tiene un puntero o una referencia a todas las variables de outerFunction, incluida outerVar. Por lo tanto, incluso cuando outerFunction ha terminado de ejecutarse, o incluso si se eliminó o se estableció en nulo, las variables en su ámbito, como la externalVar, se quedan en la memoria debido a la referencia destacada a ellas en la parte de innerFunction que se ha devuelto referenceToInnerFunction. Para liberar verdaderamente la memoria externa y el resto de las variables de outerFunction de la memoria, tendría que deshacerse de esta referencia destacada.Por ejemplo, al establecer referenceToInnerFunction en nulo también.
//////////
Otras dos cosas sobre los cierres a tener en cuenta. Primero, el cierre 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
Segundo, cuando se crea un cierre, conserva una referencia a todas las variables y funciones de su función de cierre; no llega a escoger y elegir. Y, pero así, los cierres se deben usar con moderación, o al menos con cuidado, ya que pueden requerir mucha memoria; muchas variables pueden mantenerse en la memoria mucho tiempo después de que una función contenedora haya terminado de ejecutarse.
Una respuesta para un niño de seis años (asumiendo que él sabe qué es una función y qué es una variable, y qué datos son):
Las funciones pueden devolver datos. Un tipo de datos que puede devolver desde una función es otra función. Cuando se devuelve esa nueva función, todas las variables y argumentos utilizados en la función que la creó no desaparecen. En su lugar, esa función principal "se cierra". En otras palabras, nada puede mirar dentro de él y ver las variables que utilizó, excepto la función que devolvió. Esa nueva función tiene una capacidad especial para mirar hacia atrás dentro de la función que la creó y ver los datos 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 cree un alcance más pequeño dentro de un alcance más amplio, el alcance más pequeño siempre podrá ver qué hay en el alcance más amplio.
Como padre de un niño de 6 años, actualmente enseña a niños pequeños (y es un principiante relativo a la codificación sin educación formal para que se requieran correcciones), creo que la lección se mantendría mejor a través del juego práctico. Si el niño de 6 años está listo para entender qué es un cierre, entonces ellos tienen la edad suficiente para tener una oportunidad. Yo sugeriría pegar el código en jsfiddle.net, explicar un poco y dejarlos solos para inventar una canción única. El texto explicativo a continuación es probablemente 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, medidas, observaciones o incluso simplemente descripciones de cosas. No puedes tocarlo, olerlo o saborearlo. Puedes escribirlo, hablarlo y escucharlo. Puedes usarlo para crear un toque de olor y gusto usando una computadora. Puede ser útil por una computadora usando código.
CÓDIGO: Toda la escritura anterior se llama código . Está escrito en JavaScript.
JAVASCRIPT: JavaScript es un lenguaje. Como el inglés o el francés o el chino son idiomas. Hay muchos idiomas que entienden las computadoras y otros procesadores electrónicos. Para que JavaScript sea comprendido por una computadora, se necesita un intérprete. Imagínese si un maestro que solo habla ruso viene a enseñar su clase en la escuela. Cuando el profesor dice "все садятся", la clase no entendería. Pero, afortunadamente, tienes un alumno ruso en tu clase que les dice a todos que esto significa "todos se sientan", así que todos lo hacen. La clase es como una computadora y el alumno ruso es el intérprete. Para JavaScript, el intérprete más común se llama navegador.
NAVEGADOR: cuando se conecta a Internet en una computadora, tableta o teléfono para visitar un sitio web, utiliza un navegador. Algunos ejemplos que puede saber son Internet Explorer, Chrome, Firefox y Safari. El navegador puede comprender JavaScript y decirle a la computadora lo que debe 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 una sola máquina dentro. O podría contener muchas otras pequeñas fábricas, cada una con muchas máquinas que realizan diferentes trabajos. En una fábrica de ropa de la vida real, es posible que tengas montones de telas y bobinas de hilo que entran y te salen camisetas y jeans. Nuestra fábrica de JavaScript solo procesa datos, no puede coser, perforar un agujero o fundir metal. En nuestra fábrica de JavaScript los datos entran y los datos salen.
Todo este material de datos suena un poco aburrido, pero es realmente genial; podríamos tener una función que le diga a un robot qué hacer para la cena. Digamos que te invito a ti ya tu amigo a mi casa. A ti te gustan más las piernas de pollo, me gustan las salchichas, tu amigo siempre quiere lo que quieres y mi amigo no come carne.
No tengo tiempo para ir de compras, por lo 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 el robot sirva todo al mismo tiempo. Necesitamos proporcionar la función con los datos sobre lo que nos gusta, la función podría "hablar" con el refrigerador y la función podría controlar el robot.
Una función normalmente tiene un nombre, paréntesis y llaves. Me gusta esto:
function cookMeal() { /* STUFF INSIDE THE FUNCTION */ }
Tenga en cuenta que /*...*/
y //
detenga el código que lee el navegador.
NOMBRE: Puede llamar a una función casi cualquier palabra que desee. El ejemplo "cookMeal" es típico de unir dos palabras y darle al segundo 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.
PARENTES: Los "paréntesis" o ()
son el buzón en la puerta de la fábrica de la función JavaScript o un buzón en la calle para enviar paquetes de información a la fábrica. A veces, el buzón de correos puede estar marcado, por ejemplo cookMeal(you, me, yourFriend, myFriend, fridge, dinnerTime)
, en cuyo caso usted sabe qué datos debe proporcionar.
BRACES: "Braces" que se ven así {}
son las ventanas tintadas de nuestra fábrica. Desde dentro de la fábrica se puede ver, pero desde afuera no se puede ver.
El código largo ejemplo anterior
Nuestro código comienza con la palabra función , ¡así que sabemos que es una! Luego el nombre de la función cantar , esa es mi propia descripción de lo que trata la función. Luego los paréntesis () . Los paréntesis están siempre ahí para una función. A veces están vacíos, ya veces tienen algo en Éste tiene una palabra en.: (person)
. Después de esto hay una abrazadera como esta {
. 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 */ }
Por lo tanto, esta función podría tener algo que ver con el canto 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 representan "variable". Una variable es como un sobre. En el exterior este sobre está marcado como "persona". En el interior contiene una hoja de papel con la información que nuestra función necesita, algunas letras y espacios se unen como un trozo de cuerda (se llama una cuerda) que hace que una frase lea "una anciana". Nuestro sobre podría contener otros tipos de cosas como números (llamados enteros), instrucciones (llamadas funciones), listas (llamadas matrices ). Debido a que esta variable está escrita fuera de todas las llaves {}
, y porque puede ver a través de las ventanas tintadas cuando está dentro de las llaves, esta variable se puede ver desde cualquier parte del código. Llamamos a esto una ''variable global''.
VARIABLE GLOBAL: persona es una variable global, lo que significa que si cambia su valor de "una anciana" a "un hombre joven", la persona seguirá siendo un hombre joven hasta que decida cambiarlo nuevamente y que cualquier otra función en El código puede ver que es un hombre joven. Presione el F12botón o mire la configuración de Opciones para abrir la consola del desarrollador de un navegador y escriba "persona" para ver cuál es este valor. Escriba person="a young man"
para cambiarlo y luego escriba "persona" nuevamente 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 , canta , ven y hazte con la persona !"
Cuando el navegador haya cargado el código JavaScript y haya alcanzado esta línea, se iniciará la función. Pongo la línea al final para asegurarme de que el navegador tenga toda la información que necesita para ejecutarlo.
Las funciones definen las acciones, la función principal es sobre el canto. Contiene una variable llamada firstPart que se aplica al canto de la persona que se aplica a cada uno de los versos de la canción: "Hubo" + persona + "que tragó". Si escribe firstPart en la consola, no obtendrá una respuesta porque la variable está bloqueada en 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 función big sing () . Las pequeñas fábricas dentro de la gran fábrica. Cada uno tiene sus propias llaves, lo que significa que las variables dentro de ellos no pueden verse desde el exterior. Es por eso que los nombres de las variables ( criatura y resultado ) se pueden repetir en los cierres pero con diferentes valores. Si escribe estos nombres de variable en la ventana de la consola, no obtendrá su valor porque está oculto por dos capas de ventanas polarizadas.
Todos los cierres saben lo que es la variable de la función sing () llamada firstPart , porque pueden ver desde sus ventanas tintadas.
Después de los cierres vienen las líneas.
fly();
spider();
bird();
cat();
La función sing () llamará a cada una de estas funciones en el orden en que se dan. Luego se realizará el trabajo de la función sing ().
El autor de Closures ha explicado los cierres bastante bien, explicando la razón por la que los necesitamos y también explicando el entorno léxico que es necesario para comprender los cierres.
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 LexicalEnvironment
objeto externo .
El proceso consta de dos pasos:
- Primero, cuando se crea una función f, no se crea en un espacio vacío. Hay un objeto actual LexicalEnvironment. En el caso anterior, su ventana (a no está definida en el momento de la creación de la función).
Cuando se crea una función, obtiene una propiedad oculta, llamada [[Ámbito]], que hace referencia al entorno léxico actual.
Si se lee una variable, pero no se puede encontrar en ninguna parte, se genera un error.
Funciones anidadas
Las funciones se pueden anidar una dentro de otra, formando una cadena de entornos léxicos que también se puede llamar una cadena de alcance.
Entonces, la función g tiene acceso a g, a y f.
Cierres
Una función anidada puede continuar activa después de que la función externa haya finalizado:
Marcando los entornos léxicos:
Como vemos, this.say
es una propiedad en el objeto de usuario, por lo que continúa vivo después de que el Usuario haya completado.
Y si recuerda, cuando this.say
se crea, (como todas las funciones) obtiene una referencia interna this.say.[[Scope]]
al entorno léxico actual. Por lo tanto, el entorno léxico de la ejecución del usuario actual permanece en la memoria. Todas las variables de Usuario también son sus propiedades, por lo que también se guardan cuidadosamente, no se desechan como de costumbre.
El objetivo es garantizar que si la función interna desea acceder a una variable externa en el futuro, puede hacerlo.
Para resumir:
- La función interna mantiene una referencia al entorno léxico externo.
- La función interna puede acceder a las variables desde cualquier momento, incluso si la función externa está terminada.
- El navegador mantiene el Entorno Léxico y todas sus propiedades (variables) en la memoria hasta que haya una función interna que lo referencia.
Esto se llama un cierre.
Estás durmiendo e invitas a Dan. Le dices a Dan que traiga un controlador XBox.
Dan invita a Paul. Dan le pide a Paul que traiga un controlador. ¿Cuántos controladores fueron traídos 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.");
Hace un tiempo escribí una publicación en el blog explicando los cierres. Esto es lo que dije sobre los cierres en términos de por qué querría uno.
Los cierres son una forma de permitir que una función tenga variables privadas persistentes, es decir, variables que solo una función conoce, en las que puede realizar un seguimiento de la información de los tiempos anteriores en que se ejecutó.
En ese sentido, dejan que una función actúe un poco como un objeto con atributos privados.
Publicación completa:
OK, fan de cierres de 6 años. ¿Quieres escuchar el ejemplo más simple de cierre?
Imaginemos la siguiente situación: un conductor está sentado en un automóvil. Ese carro está dentro de un avión. El avión está en el aeropuerto. La capacidad del conductor para acceder a cosas fuera de su automóvil, pero dentro del avión, incluso si ese avión sale de un aeropuerto, es un cierre. Eso es.Cuando cumpla 27 años, observe la explicación más detallada o el ejemplo a continuación.
Aquí es cómo puedo convertir mi historia 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");
Preparé un tutorial interactivo de JavaScript para explicar cómo funcionan los cierres. ¿Qué es un cierre?
Aquí está 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
Simplemente los señalaría a la página de cierres de Mozilla . Es la mejor, más concisa y simple explicación de los conceptos básicos de cierre y uso práctico que he encontrado. Es altamente recomendable para 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 los cierres, entonces es lógico que estén listos para comprender la explicación concisa y simple que se proporciona en el artículo.
Tiendo a aprender mejor por comparaciones BUENO / MALO. Me gusta ver el código de trabajo seguido del código que no funciona y que es probable que encuentre alguien. Monté un jsFiddle que hace una comparación y trata de reducirse las diferencias a las explicaciones más simples que pudiera ocurrir.
Cierres bien hechos:
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 in 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 variablen
para resaltar que es una nueva variable creada en un nuevo ámbito de función y no es la misma variableindex
que está vinculada al ámbito externo.Esto crea un nuevo alcance y
n
está vinculado a ese alcance; esto significa que tenemos 10 ámbitos separados, uno para cada iteración.createClosure(n)
devuelve una función que devuelve la n dentro de ese alcance.Dentro de cada ámbito
n
está vinculado al valor que tenía cuandocreateClosure(n)
se invocó, por lo que la función anidada que se devuelve siempre devolverá el valorn
que tenía cuandocreateClosure(n)
se invocó.
Cierres mal hechos:
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 in badArr) {
console.log(badArr[index]());
}
En el código anterior, el bucle se movió dentro de la
createClosureArray()
función y la función ahora solo devuelve la matriz completa, que a primera vista parece más intuitiva.Lo que podría no ser obvio es que ya que
createClosureArray()
solo se invoca una vez que solo se crea un ámbito para esta función en lugar de uno para cada iteración del bucle.Dentro de esta función
index
se define una variable nombrada . El bucle se ejecuta y agrega funciones a la matriz que retornaindex
. Tenga en cuenta queindex
se define dentro de lacreateClosureArray
función que solo se invoca una vez.Debido a que solo había un alcance dentro de la
createClosureArray()
función,index
solo está vinculado a un valor dentro de ese alcance. En otras palabras, cada vez que el bucle cambia el valor deindex
, lo cambia para todo lo que hace referencia dentro de ese ámbito.Todas las funciones agregadas a la matriz devuelven la MISMA
index
variable del ámbito principal donde se definió en lugar de 10 diferentes de 10 ámbitos diferentes, como el primer ejemplo. El resultado final es que las 10 funciones devuelven la misma variable desde el mismo ámbito.Una vez que el bucle terminó y
index
se modificó, el valor final era 10, por lo tanto, cada función agregada a la matriz devuelve el valor de laindex
variable única que ahora se establece en 10.
Resultado
CIERRES REALIZADOS A LA DERECHA
n = 0
n = 1
n = 2
n = 3
n = 4
n = 5
n = 6
n = 7
n = 8
n = 9CIERRES REALIZADOS INCORRECTO
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
Un cierre es muy parecido a un objeto. Se crea una instancia cuando llamas a una función.
El alcance de un cierre en JavaScript es léxico, lo que significa que todo lo que está contenido dentro de la función a la que pertenece el cierre , tiene acceso a cualquier variable que esté en él.
Una variable está contenida en el cierre si
- asignarlo con
var foo=1;
o - solo escribe
var foo;
Si una función interna (una función contenida dentro de otra función) accede a dicha variable sin definirla en su propio ámbito con var, modifica el contenido de la variable en el cierre externo .
Un cierre sobrevive al tiempo de ejecución de la función que lo generó. Si otras funciones logran salir del cierre / alcance en el que están definidas (por ejemplo, como valores de retorno), esas continuarán haciendo referencia a ese cierre .
Ejemplo
function example(closure) {
// define somevariable to live in the closure of example
var somevariable = ''unchanged'';
return {
change_to: function(value) {
somevariable = value;
},
log: function(value) {
console.log(''somevariable of closure %s is: %s'',
closure, somevariable);
}
}
}
closure_one = example(''one'');
closure_two = example(''two'');
closure_one.log();
closure_two.log();
closure_one.change_to(''some new value'');
closure_one.log();
closure_two.log();
Salida
somevariable of closure one is: unchanged
somevariable of closure two is: unchanged
somevariable of closure one is: some new value
somevariable of closure two is: unchanged
Un cierre es donde una función interna tiene acceso a variables en su función externa. Esa es probablemente la explicación de una línea más simple que puede obtener para los cierres.
Una función en JavaScript no es solo una referencia a un conjunto de instrucciones (como en 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). Tales funciones de dos piezas se llaman cierres. Cada función en JavaScript puede considerarse un cierre.
Los cierres son funciones con un estado. Es algo similar a "this" en el sentido de que "this" también proporciona un estado para una función, pero "function" y "this" son objetos separados ("this" es solo un parámetro elegante, y la única forma de vincularlo permanentemente a un La función es crear un cierre). Si bien "esto" y la función siempre viven por separado, una función no puede separarse de su cierre y el lenguaje no proporciona ningún medio para acceder a las variables capturadas.
Debido a que todas estas variables externas a las que hace referencia una función anidada léxicamente son en realidad variables locales en la cadena de sus funciones envolventes léxicas (se puede suponer que las variables globales son variables locales de alguna función raíz), y cada ejecución individual de una función crea nuevas instancias de En sus variables locales, se deduce que cada ejecución de una función que devuelve (o la transfiere de otra manera, como registrarla como una devolución de llamada) una función anidada crea un nuevo cierre (con su propio conjunto potencialmente único de variables no locales referenciadas que representan su ejecución). contexto).
Además, debe entenderse que las variables locales en JavaScript no se crean en el marco de la pila, sino en el montón y se destruyen solo cuando nadie las está haciendo referencia. Cuando una función regresa, las referencias a sus variables locales se reducen, pero aún pueden ser no nulas si durante la ejecución actual se convirtieron en parte de un cierre y aún son referenciadas por sus funciones anidadas léxicamente (lo que puede suceder solo si las referencias a estas funciones anidadas se devolvieron o se transfirieron 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();