tipos procedimiento node funciones codigos anonimas javascript scope closures

node - procedimiento javascript



Cierres de JavaScript vs. funciones anónimas (12)

Ambos están utilizando cierres.

Voy con la definición de Wikipedia aquí:

En informática, un cierre (también el cierre léxico o el cierre de una función) es una función o referencia a una función junto con un entorno de referencia: una tabla que almacena una referencia a cada una de las variables no locales (también llamadas variables libres) de esa función . Un cierre, a diferencia de un indicador de función simple, permite que una función acceda a esas variables no locales incluso cuando se las invoca fuera de su alcance léxico inmediato.

El intento de su amigo claramente usa la variable i , que no es local, tomando su valor y haciendo una copia para almacenar en el i2 local.

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 al mismo i2 . Como dentro de la función anónima interna i2 no es local, esto crea un cierre.

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 bucle de conteo y queremos imprimir la variable del contador en la consola retrasada. Por lo tanto, usamos setTimeout y cierres para capturar el valor de la variable de contador para asegurarnos de que no imprimirá N veces el valor N.

La solución incorrecta sin cierres o cualquier cosa cercana 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 bucle, es decir, 10.

Así que su intento fue:

for(var i = 0; i < 10; i++) { (function(){ var i2 = i; setTimeout(function(){ console.log(i2); }, 1000) })(); }

imprimiendo 0 a 9 como se esperaba.

Le dije que no está usando un cierre para capturar a i , pero él insiste en que sí lo está. 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 de nuevo. Lo mismo se aplica si almaceno su función en una var y la ejecuto después del bucle, también imprimiendo 10 veces 10. Así que mi argumento es que realmente no captura el valor de i , por lo que su versión no es 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 (llamado i2 dentro del cierre), pero ahora devuelvo otra función y la transfiero. En mi caso, la función pasada a setTimeout realmente captura i .

Ahora, ¿quién está usando 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 utiliza cierres para lograrlo.


Considera lo siguiente. Esto crea y recrea una función f que se cierra en i , ¡pero diferentes !:

i=100; f=function(i){return function(){return ++i}}(0); alert([f,f(),f(),f(),f(),f(),f(),f(),f(),f(),f()].join(''/n/n'')); f=function(i){return new Function(''return ++i'')}(0); /* function declarations ~= expressions! */ alert([f,f(),f(),f(),f(),f(),f(),f(),f(),f(),f()].join(''/n/n''));

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)

for(var i = 0; i < 10; i++) { setTimeout( new Function(''console.log(''+i+'')''), 1000 ); }

O para ser más explícitos:

for(var i = 0; i < 10; i++) { console.log( f = new Function( ''console.log(''+i+'')'' ) ); setTimeout( f, 1000 ); }

NÓTESE BIEN. la última definición fes function(){ console.log(9) } antes 0 de su impresión.

¡Advertencia! El concepto de cierre puede ser una distracción coercitiva de la esencia de la programación elemental:

for(var i = 0; i < 10; i++) { setTimeout( ''console.log(''+i+'')'', 1000 ); }

x-refs .:
¿Cómo funcionan los cierres de JavaScript?
Explicación de cierres de Javascript
¿Un cierre (JS) requiere una función dentro de una función?
¿Cómo entender los cierres en Javascript?
Variación de variables locales y globales de Javascript


Después de inspeccionar de cerca, parece que ambos están usando el cierre.

En el caso de sus amigos, se accede a i dentro de la función anónima 1 y se accede a i2 en la función anónima 2 donde está presente console.log .

En su caso, está accediendo a i2 dentro de la función anónima donde está presente console.log . Añadir un debugger; La declaración antes de console.log y en las herramientas de desarrollo de Chrome en "Variables de alcance" indicará en qué ámbito se encuentra la variable.


En pocas palabras, los cierres de Javascript permiten que una función acceda a una variable declarada en una función léxico-padre .

Veamos una explicación más detallada. Para entender los cierres es importante entender cómo JavaScript se ajusta a las variables.

Alcances

En JavaScript los ámbitos se definen con funciones. Cada función define un nuevo alcance.

Considere el siguiente ejemplo;

function f() {//begin of scope f var foo=''hello''; //foo is declared in scope f for(var i=0;i<2;i++){//i is declared in scope f //the for loop is not a function, therefore we are still in scope f var bar = ''Am I accessible?'';//bar is declared in scope f console.log(foo); } console.log(i); console.log(bar); }//end of scope f

llamando a f copias

hello hello 2 Am I Accessible?

Consideremos ahora el caso en el que tenemos una función g definida dentro de otra función f .

function f() {//begin of scope f function g() {//being of scope g /*...*/ }//end of scope g /*...*/ }//end of scope f

Llamaremos f al padre léxico de g . Como explicamos antes ahora tenemos 2 alcances; El alcance f y el alcance g .

Pero un ámbito está "dentro" del otro ámbito, ¿así que el ámbito de la función secundaria forma parte del ámbito de la función principal? Qué 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 las variables declaradas en el alcance g sino que también puede acceder a las variables declaradas en el alcance de la función principal f .

Considera lo siguiente

function f()//lexical parent function {//begin of scope f var foo=''hello''; //foo declared in scope f function g() {//being of scope g var bar=''bla''; //bar declared in scope g console.log(foo); }//end of scope g g(); console.log(bar); }//end of scope f

llamando a f copias

hello undefined

Veamos la línea console.log(foo); . En este punto, estamos en el alcance g e intentamos acceder a la variable foo que está declarada en el alcance f . Pero como se indicó anteriormente, podemos acceder a cualquier variable declarada en una función léxica principal, como es el caso aquí; g es el padre léxico de f . Por lo tanto hello se imprime.
Veamos ahora la línea console.log(bar); . En este punto, estamos en el alcance f e intentamos acceder a la bar variables que está declarada en el alcance g . bar no está declarada en el alcance actual y la función g no es la matriz de f , por lo tanto la bar no está definida

En realidad, también podemos acceder a las variables declaradas en el alcance de una función léxica "gran padre". Por lo tanto, si hubiera una función h definida dentro de la función g

function f() {//begin of scope f function g() {//being of scope g function h() {//being of scope h /*...*/ }//end of scope h /*...*/ }//end of scope g /*...*/ }//end of scope f

entonces h podrá acceder a todas las variables declaradas en el alcance de la función h , g y f . 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éxico gran padre, en la función léxica gran padre, 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 principal que no tiene principal primario léxico.

El objeto ventana

En realidad, la cadena no se detiene en la última función principal. Hay un alcance más especial; El alcance global . Toda variable no declarada en una función se considera declarada en el ámbito global. El ámbito global tiene dos especialidades;

  • Cada variable declarada en el ámbito global es accesible en todas partes.
  • Las variables declaradas en el ámbito global corresponden a las propiedades del objeto de window .

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 propiedad foo del objeto de ventana.

Ambos intentos utilizan cierres.

Ahora que ha leído una explicación más detallada, ahora puede ser evidente que ambas soluciones utilizan cierres. Pero para estar seguros, hagamos una prueba.

Vamos a crear un nuevo lenguaje de programación; JavaScript-No-Cierre. Como su nombre lo indica, JavaScript-No-Closure es idéntico a JavaScript, excepto que no admite Closures.

En otras palabras;

var foo = ''hello''; function f(){console.log(foo)}; f(); //JavaScript-No-Closure prints undefined //JavaSript prints hello

Bien, veamos qué sucede con la primera solución con JavaScript-No-Closure;

for(var i = 0; i < 10; i++) { (function(){ var i2 = i; setTimeout(function(){ console.log(i2); //i2 is undefined in JavaScript-No-Closure }, 1000) })(); }

por lo tanto, esto se imprimirá undefined 10 veces en JavaScript-No-Closure.

De ahí que la primera solución utilice el cierre.

Veamos la segunda solución;

for(var i = 0; i < 10; i++) { setTimeout((function(i2){ return function() { console.log(i2); //i2 is undefined in JavaScript-No-Closure } })(i), 1000); }

por lo tanto, esto se imprimirá undefined 10 veces en JavaScript-No-Closure.

Ambas soluciones utilizan cierres.

Edición: se supone que estos 3 fragmentos de código no están definidos en el ámbito global. De lo contrario, las variables foo y i estarían vinculadas al objeto de la window y, por lo tanto, serían accesibles a través del objeto de la window tanto en JavaScript como en JavaScript-No-Closure.


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, utiliza el ámbito en el que se declaró, no el ámbito en el que se llamó. En javaScript, todas las funciones se comportan así. Los valores de las variables en un alcance persisten mientras haya una función que aún los apunte. La excepción a la regla es "esto", que se refiere al objeto en el que se encuentra la función cuando se llama.

var z = 1; function x(){ var z = 2; y(function(){ alert(z); }); } function y(f){ var z = 3; f(); } x(); //alerts ''2''


Nunca he estado feliz con la forma en que alguien explica esto.

La clave para entender los cierres es entender cómo sería JS sin cierres.

Sin cierres, esto arrojaría un error.

function outerFunc(){ var outerVar = ''an outerFunc var''; return function(){ alert(outerVar); } } outerFunc()(); //returns inner function and fires it

Una vez que outerFunc ha regresado en una versión imaginaria de JavaScript deshabilitada por el cierre, la referencia a outerVar se recolectaría y se iría sin dejar nada allí para que la función interna haga referencia.

Los cierres son esencialmente las reglas especiales que activan y hacen posible que esas vars existan cuando una función interna hace referencia a las variables de una función externa. Con los cierres, las vars a las que se hace referencia se mantienen incluso después de que la función externa se haya completado o "cerrado" si eso le ayuda a recordar el punto.

Incluso con cierres, el ciclo de vida de las variables locales en una función sin funciones internas que hacen referencia a sus locales funciona de la misma manera que en una versión sin cierre. Cuando la función está terminada, los locales se recogen la basura.

Una vez que tiene una referencia en una función interna a una var externa, sin embargo, es como si una jamba de la puerta se interpusiera en el camino de 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 propio alcance del 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 continúa incrementando y registrando la var local de una función externa continuará alertando valores más altos.

function outerFunc(){ var incrementMe = 0; return function(){ incrementMe++; console.log(incrementMe); } } var inc = outerFunc(); inc(); //logs 1 inc(); //logs 2


Según la definición de closure :

Un "cierre" es una expresión (normalmente una función) que puede tener variables libres junto con un entorno que vincula esas variables (que "cierra" la expresión).

Está utilizando el 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).


Tú y tu amigo usan cierres:

Un cierre es un tipo especial de objeto que combina dos cosas: una función y el entorno en el que se creó esa función. El entorno consta de cualquier variable local que estaba dentro del alcance en el momento en que se creó el cierre.

MDN: interesting

En la función de function(){ console.log(i2); } de código de su amigo function(){ console.log(i2); } function(){ console.log(i2); } definido dentro del cierre de la función de function(){ var i2 = i; ... anónima function(){ var i2 = i; ... function(){ var i2 = i; ... y puede leer / escribir la variable local i2 .

En su función de function(){ console.log(i2); } código function(){ console.log(i2); } function(){ console.log(i2); } definido dentro del cierre de la función de function(i2){ return ... y puede leer / escribir el i2 local valioso (declarado en este caso como un parámetro).

En ambos casos, function function(){ console.log(i2); } function(){ console.log(i2); } luego pasó a setTimeout .

Otro equivalente (pero con menos uso de memoria) es:

function fGenerator(i2){ return function(){ console.log(i2); } } for(var i = 0; i < 10; i++) { setTimeout(fGenerator(i), 1000); }


Veamos ambas maneras:

(function(){ var i2 = i; setTimeout(function(){ console.log(i2); }, 1000) })();

Declara y ejecuta inmediatamente una función anónima que ejecuta setTimeout() dentro de su propio contexto. El valor actual de i se conserva haciendo primero una copia en i2 ; Funciona por la ejecución inmediata.

setTimeout((function(i2){ return function() { console.log(i2); } })(i), 1000);

Declara un contexto de ejecución para la función interna mediante el cual el valor actual de i se conserva en i2 ; 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 pasa a setTimeout() mientras que su función interna llama a setTimeout() sí misma.

Envolver ambos códigos dentro de otro setTimeout() no prueba que solo el segundo enfoque use cierres, simplemente no hay lo mismo para empezar.

Conclusión

Ambos métodos utilizan cierres, por lo que se reduce al gusto personal; El segundo enfoque es más fácil de "moverse" o generalizar.


Cierre

Un cierre no es una función, y no una expresión. Se debe ver como una especie de "instantánea" de las variables utilizadas fuera de las funciones y se utiliza dentro de la función. Gramaticalmente, uno debería decir: ''tomar el cierre de las variables''.

Nuevamente, en otras palabras: un cierre es una copia del contexto relevante de las variables de las que 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 las variables, no se pasa como parámetro, y dentro de su alcance (léxico), se ha realizado un cierre.

Entonces, con respecto a sus ejemplos:

// 1 for(var i = 0; i < 10; i++) { setTimeout(function() { console.log(i); // closure, only when loop finishes within 1000 ms, }, 1000); // i = 10 for all functions } // 2 for(var i = 0; i < 10; i++) { (function(){ var i2 = i; // closure of i (lexical scope: for-loop) setTimeout(function(){ console.log(i2); // closure of i2 (lexical scope:outer function) }, 1000) })(); } // 3 for(var i = 0; i < 10; i++) { setTimeout((function(i2){ return function() { console.log(i2); // closure of i2 (outer scope) } })(i), 1000); // param access i (no closure) }

Todos están utilizando cierres. No confundir 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!


Nota del editor: Todas las funciones en JavaScript son cierres como se explica en esta post . Sin embargo, solo nos interesa identificar un subconjunto de estas funciones que sean interesting desde un punto de vista teórico. De aquí 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:

  1. Tome una función. Llamémoslo F.
  2. Listar todas las variables de F.
  3. Las variables pueden ser de dos tipos:
    1. Variables locales (variables encuadernadas)
    2. Variables no locales (variables libres)
  4. Si F no tiene variables libres, entonces no puede ser un cierre.
  5. Si F tiene alguna variable libre (que se define en un ámbito principal de F), entonces:
    1. Solo debe haber un alcance principal de F al que se enlaza una variable libre.
    2. Si se hace referencia a F desde fuera de ese ámbito principal, entonces se convierte en un cierre para esa variable libre.
    3. Esa variable libre se denomina valor ascendente del cierre F.

Ahora, usemos esto para averiguar quién usa los cierres y quién no (por el bien de la explicación que he nombrado las funciones):

Caso 1: El programa de tu amigo

for (var i = 0; i < 10; i++) { (function f() { var i2 = i; setTimeout(function g() { console.log(i2); }, 1000); })(); }

En el programa anterior hay dos funciones: f y g . A ver si son cierres:

Para f :

  1. Listar las variables:
    1. i2 es una variable local .
    2. i es una variable libre .
    3. setTimeout es una variable libre .
    4. g es una variable local .
    5. console es una variable libre .
  2. Encuentre el ámbito principal al que está vinculada cada variable libre:
    1. i está vinculado al ámbito global.
    2. setTimeout está vinculado al ámbito global.
    3. console está vinculada al ámbito global.
  3. ¿En qué ámbito se hace referencia a la función? El ámbito global .
    1. Por lo tanto, i no está cerrado por f .
    2. Por setTimeout tanto, setTimeout no está cerrado por f .
    3. Por lo tanto, la console no está cerrada por f .

Así, la función f no es un cierre.

Para g :

  1. Listar las variables:
    1. console es una variable libre .
    2. i2 es una variable libre .
  2. Encuentre el ámbito principal al que está vinculada cada variable libre:
    1. console está vinculada al ámbito global.
    2. i2 está ligado al alcance de f .
  3. ¿En qué ámbito se hace referencia a la función? El alcance de setTimeout .
    1. Por lo tanto, la console no está cerrada por g .
    2. Por lo tanto, i2 está cerrado por g .

Por lo tanto, la función g es un cierre para la variable libre i2 (que es un valor ascendente para g ) cuando se hace referencia desde setTimeout .

Malo para ti: tu amigo está usando un cierre. La función interior es un cierre.

Caso 2: su programa

for (var i = 0; i < 10; i++) { setTimeout((function f(i2) { return function g() { console.log(i2); }; })(i), 1000); }

En el programa anterior hay dos funciones: f y g . A ver si son cierres:

Para f :

  1. Listar las variables:
    1. i2 es una variable local .
    2. g es una variable local .
    3. console es una variable libre .
  2. Encuentre el ámbito principal al que está vinculada cada variable libre:
    1. console está vinculada al ámbito global.
  3. ¿En qué ámbito se hace referencia a la función? El ámbito global .
    1. Por lo tanto, la console no está cerrada por f .

Así, la función f no es un cierre.

Para g :

  1. Listar las variables:
    1. console es una variable libre .
    2. i2 es una variable libre .
  2. Encuentre el ámbito principal al que está vinculada cada variable libre:
    1. console está vinculada al ámbito global.
    2. i2 está ligado al alcance de f .
  3. ¿En qué ámbito se hace referencia a la función? El alcance de setTimeout .
    1. Por lo tanto, la console no está cerrada por g .
    2. Por lo tanto, i2 está cerrado por g .

Por lo tanto, la función g es un cierre para la variable libre i2 (que es un valor ascendente para g ) cuando se hace referencia desde setTimeout .

Bien por ti: estás utilizando un cierre. La función interior es un cierre.

Así que tú y tu amigo están usando cierres. Para de discutir. Espero haber borrado el concepto de cierres y cómo identificarlos para los dos.

Edición: una explicación simple de por qué todas las funciones se cierran (créditos @Peter):

Primero consideremos el siguiente programa (es el control ):

lexicalScope(); function lexicalScope() { var message = "This is the control. You should be able to see this message being alerted."; regularFunction(); function regularFunction() { alert(eval("message")); } }

  1. Sabemos que tanto lexicalScope como regularFunction no son cierres de la definición anterior .
  2. Cuando ejecutamos el programa , esperamos que el message sea ​​alertado porque regularFunction no es un cierre (es decir, tiene acceso a todas las variables en su ámbito principal, incluido el message ).
  3. Cuando ejecutamos el programa observamos que el message es efectivamente alertado.

A continuación consideremos el siguiente programa (es la alternative ):

var closureFunction = lexicalScope(); closureFunction(); function lexicalScope() { var message = "This is the alternative. If you see this message being alerted then in means that every function in JavaScript is a closure."; return function closureFunction() { alert(eval("message")); }; }

  1. Sabemos que solo closureFunction es un cierre de la definición anterior .
  2. Cuando ejecutamos el programa , esperamos que el message no sea alertado porque closureFunction 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 incluye el message ).
  3. Cuando ejecutamos el programa observamos que el message está siendo alertado.

¿Qué inferimos de esto?

  1. Los intérpretes de JavaScript no tratan los cierres de manera diferente a la forma en que tratan otras funciones.
  2. Cada función lleva su cadena de alcance junto con él. Los cierres no tienen un entorno de referencia separado .
  3. Un cierre es como cualquier otra función. Simplemente los llamamos cierres cuando se hace referencia a un alcance fuera del alcance al que pertenecen porque este es un caso interesante.

Me gustaría compartir mi ejemplo y una explicación sobre los cierres. Hice un ejemplo de python, y dos figuras para demostrar estados de pila.

def maker(a, b, n): margin_top = 2 padding = 4 def message(msg): print(''/n’ * margin_top, a * n, '' ‘ * padding, msg, '' ‘ * padding, b * n) return message f = maker(''*'', ''#'', 5) g = maker('''', ''♥’, 3) … f(''hello'') g(‘good bye!'')

La salida de este código sería la siguiente:

***** hello #####  good bye! ♥♥♥

Aquí hay dos figuras para mostrar 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 función funcione, el marco de pila de la función de fabricante que desapareció hace mucho tiempo debe ser accesible, que se encuentra en el cierre que podemos encontrar junto con el objeto de mensaje de función.