Un amigo mío y yo estamos discutiendo qué es un cierre en JS y qué no. Solo queremos asegurarnos de que realmente lo entendemos correctamente.
Tomemos este ejemplo. Tenemos un ciclo de conteo y queremos imprimir la variable de contador en la consola con retraso. Por lo tanto, usamos setTimeout
y cierres para capturar el valor de la variable de contador para asegurarnos de que no imprima N veces el valor N.
La solución incorrecta sin cierres ni nada cercano a los cierres sería:
for(var i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
que, por supuesto, imprimirá 10 veces el valor de i
después del ciclo, es decir, 10.
Entonces su intento fue:
for(var i = 0; i < 10; i++) {
(function(){
var i2 = i;
setTimeout(function(){
console.log(i2);
}, 1000)
})();
}
imprimiendo de 0 a 9 como se esperaba.
Le dije que no está usando un cierre para capturar i
, pero insiste en que sí. Probé que no usa cierres colocando el cuerpo del bucle for dentro de otro setTimeout
(pasando su función anónima a setTimeout
), imprimiendo 10 veces 10 nuevamente. Lo mismo se aplica si almaceno su función en a var
y la ejecuto después del ciclo, también imprimiendo 10 veces 10. Entonces, mi argumento es que realmente no captura el valor dei
, haciendo que su versión no sea un cierre.
Mi intento fue:
for(var i = 0; i < 10; i++) {
setTimeout((function(i2){
return function() {
console.log(i2);
}
})(i), 1000);
}
Así que capturo i
(nombrado i2
dentro del cierre), pero ahora devuelvo otra función y paso esto. En mi caso, la función pasada a setTimeout realmente captura i
.
¿Ahora quién usa los cierres y quién no?
Tenga en cuenta que ambas soluciones imprimen de 0 a 9 en la consola con retraso, por lo que resuelven el problema original, pero queremos entender cuál de esas dos soluciones usa cierres para lograr esto.
fuente
Respuestas:
Nota del editor: Todas las funciones en JavaScript son cierres como se explica en esta publicación . Sin embargo, solo nos interesa identificar un subconjunto de estas funciones que sean interesantes desde un punto de vista teórico. En adelante, cualquier referencia a la palabra cierre se referirá a este subconjunto de funciones a menos que se indique lo contrario.
Una explicación simple para los cierres:
Ahora usemos esto para descubrir quién usa los cierres y quién no (en aras de la explicación, he nombrado las funciones):
Caso 1: el programa de tu amigo
En el programa anterior hay dos funciones:
f
yg
. Veamos si son cierres:Para
f
:i2
es una variable locali
Es una variable libre .setTimeout
Es una variable libre .g
es una variable localconsole
Es una variable libre .i
está vinculado al alcance global.setTimeout
está vinculado al alcance global.console
está vinculado al alcance global.i
lo tanto, no está cerrado porf
.setTimeout
lo tanto, no está cerrado porf
.console
lo tanto, no está cerrado porf
.Por lo tanto, la función
f
no es un cierre.Para
g
:console
Es una variable libre .i2
Es una variable libre .console
está vinculado al alcance global.i2
está vinculado al alcance def
.setTimeout
.console
lo tanto, no está cerrado porg
.i2
lo tanto está cerrado porg
.Por lo tanto, la función
g
es un cierre para la variable librei2
(que es un valor ascendenteg
) cuando se hace referencia desde dentrosetTimeout
.Malo para ti: tu amigo está usando un cierre. La función interna es un cierre.
Caso 2: su programa
En el programa anterior hay dos funciones:
f
yg
. Veamos si son cierres:Para
f
:i2
es una variable localg
es una variable localconsole
Es una variable libre .console
está vinculado al alcance global.console
lo tanto, no está cerrado porf
.Por lo tanto, la función
f
no es un cierre.Para
g
:console
Es una variable libre .i2
Es una variable libre .console
está vinculado al alcance global.i2
está vinculado al alcance def
.setTimeout
.console
lo tanto, no está cerrado porg
.i2
lo tanto está cerrado porg
.Por lo tanto, la función
g
es un cierre para la variable librei2
(que es un valor ascendenteg
) cuando se hace referencia desde dentrosetTimeout
.Bien por ti: estás usando un cierre. La función interna es un cierre.
Entonces, tanto usted como su amigo están utilizando cierres. Deja de discutir. Espero haber aclarado el concepto de cierres y cómo identificarlos para los dos.
Editar: Una explicación simple de por qué todas las funciones se cierran (créditos @Peter):
Primero consideremos el siguiente programa (es el control ):
lexicalScope
yregularFunction
no son cierres de la definición anterior .message
recibir alertas porqueregularFunction
no es un cierre (es decir, tiene acceso a todas las variables en su ámbito principal, incluidomessage
).message
hecho está alertado.A continuación, consideremos el siguiente programa (es la alternativa ):
closureFunction
es un cierre de la definición anterior .message
no recibir alertas porqueclosureFunction
es un cierre (es decir, solo tiene acceso a todas sus variables no locales en el momento en que se crea la función ( ver esta respuesta ), esto no incluyemessage
).message
realidad está siendo alertado.¿Qué inferimos de esto?
fuente
g
ejecuta en el alcance desetTimeout
, pero en el caso 2 dice que sef
ejecuta en el alcance global. Ambos están dentro de setTimeout, entonces, ¿cuál es la diferencia?Según la
closure
definición:Está utilizando
closure
si define una función que utiliza una variable que se define fuera de la función. (llamamos a la variable una variable libre ).Todos usan
closure
(incluso en el 1er ejemplo).fuente
i2
que se define fueraEn pocas palabras, los cierres de Javascript permiten que una función acceda a una variable que se declara en una función léxica-primaria .
Veamos una explicación más detallada. Para comprender los cierres, es importante comprender cómo JavaScript define las variables.
Alcances
En JavaScript, los ámbitos se definen con funciones. Cada función define un nuevo alcance.
Considere el siguiente ejemplo;
llamando f impresiones
Consideremos ahora el caso en que tenemos una función
g
definida dentro de otra funciónf
.Llamaremos
f
al padre léxico deg
. Como se explicó antes, ahora tenemos 2 ámbitos; El alcancef
y el alcanceg
.Pero un alcance está "dentro" del otro alcance, entonces, ¿el alcance de la función secundaria es parte del alcance de la función primaria? Lo que sucede con las variables declaradas en el alcance de la función padre; ¿podré acceder a ellos desde el alcance de la función secundaria? Ahí es exactamente donde intervienen los cierres.
Cierres
En JavaScript, la función
g
no solo puede acceder a ninguna variable declarada en el alcance,g
sino también a cualquier variable declarada en el alcance de la función principalf
.Considera seguir;
llamando f impresiones
Miremos la línea
console.log(foo);
. En este punto, estamos dentro del alcanceg
e intentamos acceder a la variablefoo
que se declara dentro del alcancef
. Pero como se indicó anteriormente, podemos acceder a cualquier variable declarada en una función principal léxica, que es el caso aquí;g
es el padre léxico def
. Porhello
lo tanto está impreso.Veamos ahora la línea
console.log(bar);
. En este punto, estamos dentro del alcancef
e intentamos acceder a la variablebar
que se declara dentro del alcanceg
.bar
no se declara en el ámbito actual y la funcióng
no es la principalf
, porbar
lo tanto, no está definidaEn realidad, también podemos acceder a las variables declaradas en el ámbito de una función léxica "gran padre". Por lo tanto, si hubiera una función
h
definida dentro de la funcióng
entonces
h
sería capaz de acceder a todas las variables declaradas en el ámbito de la funciónh
,g
yf
. Esto se hace con cierres . En JavaScript, los cierres nos permiten acceder a cualquier variable declarada en la función léxica principal, en la función léxica principal, en la función léxica principal, etc. Esto puede verse como una cadena de alcance ;scope of current function -> scope of lexical parent function -> scope of lexical grand parent function -> ...
hasta la última función padre que no tiene padre léxico.El objeto ventana
En realidad, la cadena no se detiene en la última función principal. Hay un alcance especial más; El alcance global . Cada variable no declarada en una función se considera declarada en el ámbito global. El alcance global tiene dos especialidades;
window
objeto.Por lo tanto, hay exactamente dos formas de declarar una variable
foo
en el ámbito global; ya sea al no declararlo en una función o al establecer la propiedadfoo
del objeto de ventana.Ambos intentos usan cierres
Ahora que ha leído una explicación más detallada, puede ser evidente que ambas soluciones usan cierres. Pero para estar seguros, hagamos una prueba.
Creemos un nuevo lenguaje de programación; JavaScript sin cierre. Como su nombre indica, JavaScript-No-Closure es idéntico a JavaScript, excepto que no admite Closures.
En otras palabras;
Muy bien, veamos qué sucede con la primera solución con JavaScript-No-Closure;
por lo tanto, esto se imprimirá
undefined
10 veces en JavaScript-No-Closure.Por lo tanto, la primera solución utiliza el cierre.
Veamos la segunda solución;
por lo tanto, esto se imprimirá
undefined
10 veces en JavaScript-No-Closure.Ambas soluciones utilizan cierres.
Editar: se supone que estos 3 fragmentos de código no están definidos en el ámbito global. De lo contrario, las variables
foo
y sei
unirían alwindow
objeto y, por lo tanto, serían accesibles a través delwindow
objeto en JavaScript y JavaScript-No-Closure.fuente
i
ser indefinido? Simplemente consulte el ámbito principal, que sigue siendo válido si no hubo cierres.i2
ai
en el momento de definir su función. Esto hace quei
NO sea una variable libre. Aún así, consideramos que su función es un cierre, pero sin ninguna variable libre, ese es el punto.Nunca he estado contento con la forma en que alguien explica esto.
La clave para comprender los cierres es comprender cómo sería JS sin cierres.
Sin cierres, esto arrojaría un error
Una vez que externalFunc ha regresado en una versión imaginaria de JavaScript deshabilitado para el cierre, la referencia a outsideVar sería recogida de basura y se iría sin dejar nada allí para que la función interna haga referencia.
Los cierres son esencialmente las reglas especiales que intervienen y hacen posible que existan esos vars cuando una función interna hace referencia a las variables de una función externa. Con los cierres, los vars a los que se hace referencia se mantienen incluso después de que se realiza la función externa o se 'cierran' si eso le ayuda a recordar el punto.
Incluso con los cierres, el ciclo de vida de los vars locales en una función sin funciones internas que hacen referencia a sus locales funciona igual que en una versión sin cierre. Cuando finaliza la función, los locales obtienen la basura.
Una vez que tiene una referencia en un func interno a una var externa, sin embargo, es como si una jamba de puerta se interpusiera en la recolección de basura para esos vars referenciados.
Una forma quizás más precisa de ver los cierres, es que la función interna básicamente usa el alcance interno como su propia foudnation de alcance.
Pero el contexto al que se hace referencia es, de hecho, persistente, no como una instantánea. La activación repetida de una función interna devuelta que sigue incrementándose y registrando la var local de una función externa seguirá alertando los valores más altos.
fuente
Ambos están usando cierres.
Voy con la definición de Wikipedia aquí:
El intento de su amigo usa claramente la variable
i
, que no es local, al tomar su valor y hacer una copia para almacenarla en el locali2
.Su propio intento pasa
i
(que en el sitio de la llamada está dentro del alcance) a una función anónima como argumento. Esto no es un cierre hasta ahora, pero esa función devuelve otra función que hace referencia a la mismai2
. Como el interior de la función anónima internai2
no es local, esto crea un cierre.fuente
i
ai2
, a continuación se definen algunos lógica y ejecuta esta función. Si no lo ejecutara inmediatamente, pero lo almacenara en una variable y lo ejecutara después del ciclo, imprimiría 10, ¿no? Entonces no capturó i.i
muy bien. El comportamiento que está describiendo no es un resultado de cierre versus no cierre; es el resultado de que la variable cerrada se haya cambiado mientras tanto. Está haciendo lo mismo usando una sintaxis diferente al llamar inmediatamente a una función y pasarlai
como argumento (que copia su valor actual en el acto). Si pones el tuyosetTimeout
dentro de otrosetTimeout
, sucederá lo mismo.Tanto usted como su amigo usan cierres:
En el código de su amigo, la función se
function(){ console.log(i2); }
define dentro del cierre de la función anónimafunction(){ var i2 = i; ...
y puede leer / escribir la variable locali2
.En su código, la función se
function(){ console.log(i2); }
define dentro del cierre de la funciónfunction(i2){ return ...
y puede leer / escribir localmente valiosoi2
(declarado en este caso como un parámetro).En ambos casos, la función
function(){ console.log(i2); }
pasó asetTimeout
.Otro equivalente (pero con menos utilización de memoria) es:
fuente
Cierre
Un cierre no es una función, y no una expresión. Debe verse como una especie de 'instantánea' de las variables utilizadas fuera del alcance de la función y utilizada dentro de la función. Gramaticalmente, uno debería decir: 'toma el cierre de las variables'.
Nuevamente, en otras palabras: un cierre es una copia del contexto relevante de variables de las cuales depende la función.
Una vez más (naïf): un cierre es tener acceso a variables que no se pasan como parámetro.
Tenga en cuenta que estos conceptos funcionales dependen en gran medida del lenguaje / entorno de programación que utilice. En JavaScript, el cierre depende del alcance léxico (que es cierto en la mayoría de los lenguajes c).
Entonces, devolver una función es principalmente devolver una función anónima / sin nombre. Cuando la función accede a variables, no pasadas como parámetro, y dentro de su alcance (léxico), se ha tomado un cierre.
Entonces, con respecto a sus ejemplos:
Todos usan cierres. No confunda el punto de ejecución con los cierres. Si la 'instantánea' de los cierres se toma en el momento equivocado, los valores pueden ser inesperados, ¡pero ciertamente se toma un cierre!
fuente
Veamos las dos formas:
Declara e inmediatamente ejecuta una función anónima que se ejecuta
setTimeout()
dentro de su propio contexto. El valor actual dei
se conserva haciendo una copia eni2
primer lugar; Funciona debido a la ejecución inmediata.Declara un contexto de ejecución para la función interna mediante el cual
i
se conserva el valor actual dei2
; Este enfoque también utiliza la ejecución inmediata para preservar el valor.Importante
Cabe mencionar que la semántica de ejecución NO es la misma entre ambos enfoques; su función interna se transfiere a
setTimeout()
mientras que su función interna se llama asetTimeout()
sí misma.Ajustar ambos códigos dentro de otro
setTimeout()
no prueba que solo el segundo enfoque use cierres, simplemente no es lo mismo para empezar.Conclusión
Ambos métodos usan cierres, por lo que se reduce al gusto personal; El segundo enfoque es más fácil de "mover" o generalizar.
fuente
setTimeout()
?i
se puede cambiar sin afectar lo que la función debería imprimir, sin depender de dónde o cuándo lo ejecutamos.()
, pasando así una función, y verá 10 veces la salida10
.()
es exactamente lo que hace que su código funcione, al igual que tu(i)
; no solo envolvió su código, le hizo cambios ... por lo tanto, ya no puede hacer una comparación válida.Escribí esto hace un tiempo para recordarme qué es un cierre y cómo funciona en JS.
Un cierre es una función que, cuando se llama, usa el alcance en el que se declaró, no el alcance en el que se llamó. En javaScript, todas las funciones se comportan así. Los valores variables en un ámbito persisten mientras exista una función que aún los señale. La excepción a la regla es 'esto', que se refiere al objeto en el que se encuentra la función cuando se llama.
fuente
Después de inspeccionar de cerca, parece que ambos están usando el cierre.
En el caso de sus amigos,
i
se accede dentro de la función anónima 1 yi2
se accede en la función anónima 2 dondeconsole.log
está presente.En su caso, está accediendo
i2
dentro de la función anónima dondeconsole.log
está presente. Agregue unadebugger;
declaración antesconsole.log
y en las herramientas de desarrollador de Chrome en "Variables de alcance" que le dirá bajo qué alcance es la variable.fuente
Considera lo siguiente. ¡Esto crea y recrea una función
f
que se cierrai
, pero diferentes !:mientras que lo siguiente se cierra en "una" función "en sí"
(¡ellos mismos! el fragmento después de esto usa un solo referente
f
)o para ser más explícito:
NÓTESE BIEN. se imprime la última definición de
f
esfunction(){ console.log(9) }
antes0
.¡Consideración! El concepto de cierre puede ser una distracción coercitiva de la esencia de la programación primaria:
x-refs .:
¿Cómo funcionan los cierres de JavaScript?
Explicaciones de cierres de Javascript
¿Un cierre (JS) requiere una función dentro de una función
¿Cómo entender los cierres en Javascript?
Confusión de variables locales y globales de Javascript
fuente
Run' only was desired - not sure how to remove the
Copy`Me gustaría compartir mi ejemplo y una explicación sobre los cierres. Hice un ejemplo de Python y dos figuras para demostrar los estados de la pila.
El resultado de este código sería el siguiente:
Aquí hay dos figuras para mostrar las pilas y el cierre adjunto al objeto de función.
cuando la función es devuelta por el fabricante
cuando la función se llama más tarde
Cuando se llama a la función a través de un parámetro o una variable no local, el código necesita enlaces de variables locales como margin_top, padding y a, b, n. Para garantizar que el código de la función funcione, el marco de la pila de la función del fabricante que desapareció hace mucho tiempo debe ser accesible, lo que está respaldado en el cierre que podemos encontrar junto con el objeto del mensaje de la función.
fuente
delete
enlace debajo de la respuesta.