Cierre de JavaScript frente a funciones anónimas

562

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 setTimeouty 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 idespué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 vary 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 i2dentro 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.

Leemes
fuente
1
@leemes: Vea mi edición ninja para un segundo enlace.
Blender
2
acabamos de llegar a un acuerdo: el que tenga razón obtendrá los puntos SO relacionados con esta pregunta
brillante
1
@leemes - Ustedes dos están usando cierres. Ambos han hecho dos funciones: una función externa y una función interna; y sus dos funciones internas son cierres. Todas sus funciones son lambdas ( funciones anónimas ). Lee mi respuesta para los detalles.
Aadit M Shah
1
@blesh: no tengo idea de qué es un cierre modificado. Veo que su enlace apunta al código C #. ¿Los cierres modificados son compatibles con JavaScript?
Aadit M Shah

Respuestas:

650

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:

  1. Toma una función. Llamémoslo F.
  2. Lista todas las variables de F.
  3. Las variables pueden ser de dos tipos:
    1. Variables locales (variables ligadas)
    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 primario de F), entonces:
    1. Debe haber solo un ámbito primario de F al que se vincula una variable libre.
    2. Si se hace referencia a F desde fuera de ese ámbito primario, se convierte en un cierre para esa variable libre.
    3. Esa variable libre se llama un valor ascendente del cierre F.

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

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: fy g. Veamos si son cierres:

Para f:

  1. Liste las variables:
    1. i2es una variable local
    2. iEs una variable libre .
    3. setTimeoutEs una variable libre .
    4. ges una variable local
    5. consoleEs una variable libre .
  2. Encuentre el ámbito primario al que está vinculada cada variable libre:
    1. iestá vinculado al alcance global.
    2. setTimeoutestá vinculado al alcance global.
    3. consoleestá vinculado al alcance global.
  3. ¿En qué ámbito se hace referencia a la función ? El alcance global .
    1. Por ilo tanto, no está cerrado por f.
    2. Por setTimeoutlo tanto, no está cerrado por f.
    3. Por consolelo tanto, no está cerrado por f.

Por lo tanto, la función fno es un cierre.

Para g:

  1. Liste las variables:
    1. consoleEs una variable libre .
    2. i2Es una variable libre .
  2. Encuentre el ámbito primario al que está vinculada cada variable libre:
    1. consoleestá vinculado al alcance global.
    2. i2está vinculado al alcance de f.
  3. ¿En qué ámbito se hace referencia a la función ? El alcance desetTimeout .
    1. Por consolelo tanto, no está cerrado por g.
    2. Por i2lo tanto está cerrado por g.

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

Malo para ti: tu amigo está usando un cierre. La función interna 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: fy g. Veamos si son cierres:

Para f:

  1. Liste las variables:
    1. i2es una variable local
    2. ges una variable local
    3. consoleEs una variable libre .
  2. Encuentre el ámbito primario al que está vinculada cada variable libre:
    1. consoleestá vinculado al alcance global.
  3. ¿En qué ámbito se hace referencia a la función ? El alcance global .
    1. Por consolelo tanto, no está cerrado por f.

Por lo tanto, la función fno es un cierre.

Para g:

  1. Liste las variables:
    1. consoleEs una variable libre .
    2. i2Es una variable libre .
  2. Encuentre el ámbito primario al que está vinculada cada variable libre:
    1. consoleestá vinculado al alcance global.
    2. i2está vinculado al alcance de f.
  3. ¿En qué ámbito se hace referencia a la función ? El alcance desetTimeout .
    1. Por consolelo tanto, no está cerrado por g.
    2. Por i2lo tanto está cerrado por g.

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

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();

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 ambos lexicalScopey regularFunctionno son cierres de la definición anterior .
  2. Cuando ejecutamos el programa , esperamos message recibir alertas porque regularFunction no es un cierre (es decir, tiene acceso a todas las variables en su ámbito principal, incluido message).
  3. Cuando ejecutamos el programa , observamos que de messagehecho está alertado.

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

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 closureFunctiones un cierre de la definición anterior .
  2. Cuando ejecutamos el programa , esperamos message no recibir alertas 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 message).
  3. Cuando ejecutamos el programa , observamos que en messagerealidad 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 ella. 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 ellos en un ámbito fuera del ámbito al que pertenecen porque este es un caso interesante.
Aadit M Shah
fuente
40
Aceptado porque entras mucho en detalles, explicando muy bien lo que está sucediendo. Y finalmente, ahora entendí mejor qué es un cierre, o mejor dicho: cómo funciona el enlace variable en JS.
leemes
3
En el caso 1, usted dice que se gejecuta en el alcance de setTimeout, pero en el caso 2 dice que se fejecuta en el alcance global. Ambos están dentro de setTimeout, entonces, ¿cuál es la diferencia?
rosscj2533
99
¿Podría indicar sus fuentes para esto? Nunca he visto una definición en la que una función podría ser un cierre si se llama en un ámbito pero no en otro. Por lo tanto, esta definición parece un subconjunto de la definición más general a la que estoy acostumbrado (ver la respuesta de kev ) donde un cierre es un cierre, independientemente del alcance que se llame, ¡o incluso si nunca se llama!
Briguy37
11
@AaditMShah Estoy de acuerdo con usted sobre qué es un cierre, pero usted habla como si hubiera una diferencia entre las funciones regulares y los cierres en JavaScript. No hay diferencia; internamente, cada función llevará consigo una referencia a la cadena de alcance particular en la que se creó. El motor JS no lo considera un caso diferente. No hay necesidad de una lista de verificación complicada; solo sepa que cada objeto de función tiene un alcance léxico. El hecho de que las variables / propiedades estén disponibles globalmente no hace que la función sea menos cerrada (es solo un caso inútil).
Peter
13
@ Peter - Sabes qué, estás en lo correcto. No hay diferencia entre una función regular y un cierre. Hice una prueba para probar esto y resulta a tu favor: aquí está el control y aquí está la alternativa . Lo que dices tiene sentido. El intérprete de JavaScript necesita hacer una contabilidad especial para cierres. Son simplemente subproductos de un lenguaje de ámbito léxico con funciones de primera clase. Mi conocimiento se limitaba a lo que leía (que era falso). Gracias por corregirme. Actualizaré mi respuesta para reflejar lo mismo.
Aadit M Shah
96

Según la closuredefinición:

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

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

kev
fuente
1
¿Cómo utiliza la tercera versión una variable definida fuera de la función?
Jon
1
@Jon el uso de la función devuelta i2que se define fuera
kev
1
@kev Está utilizando el cierre si define una función que utiliza una variable que se define fuera de la función ...... luego en "Caso 1: El programa de su amigo" de "Aadit M Shah" la respuesta es "función f" un cierre? utiliza la i (variable que se define fuera de la función). ¿El alcance global hace referencia a un determinante?
internos-en
54

En 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;

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 f impresiones

hello
hello
2
Am I Accessible?

Consideremos ahora el caso en que tenemos una función gdefinida 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 fal padre léxico de g. Como se explicó antes, ahora tenemos 2 ámbitos; El alcance fy el alcance g.

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 gno solo puede acceder a ninguna variable declarada en el alcance, gsino también a cualquier variable declarada en el alcance de la función principal f.

Considera seguir;

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 f impresiones

hello
undefined

Miremos la línea console.log(foo);. En este punto, estamos dentro del alcance ge intentamos acceder a la variable fooque se declara dentro del alcance f. Pero como se indicó anteriormente, podemos acceder a cualquier variable declarada en una función principal léxica, que es el caso aquí; ges el padre léxico de f. Por hellolo tanto está impreso.
Veamos ahora la línea console.log(bar);. En este punto, estamos dentro del alcance fe intentamos acceder a la variable barque se declara dentro del alcance g. barno se declara en el ámbito actual y la función gno es la principal f, por barlo tanto, no está definida

En 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 hdefinida dentro de la funcióng

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 hsería capaz de acceder a todas las variables declaradas en el ámbito de la función h, gy 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é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;

  • todas las variables declaradas en el alcance global son accesibles desde cualquier lugar
  • Las variables declaradas en el ámbito global corresponden a las propiedades del windowobjeto.

Por lo tanto, hay exactamente dos formas de declarar una variable fooen el ámbito global; ya sea al no declararlo en una función o al establecer la propiedad foodel 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;

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

Muy 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á undefined10 veces en JavaScript-No-Closure.

Por lo tanto, la primera solución utiliza 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á undefined10 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 fooy se iunirían al windowobjeto y, por lo tanto, serían accesibles a través del windowobjeto en JavaScript y JavaScript-No-Closure.

brillout.com
fuente
¿Por qué debería iser indefinido? Simplemente consulte el ámbito principal, que sigue siendo válido si no hubo cierres.
Leemes
por la misma razón que foo no está definido en JavaScript-No-Closure. <code> i </code> no está indefinido en JavaScript gracias a una función en JavaScript que permite acceder a las variables definidas en el padre léxico. Esta característica se llama cierre.
brillante
No entendió la diferencia entre referirse a variables ya definidas y variables libres . En los cierres, definimos variables libres que deben vincularse en el contexto externo. En su código, que acaba de establecer i2 a ien el momento de definir su función. Esto hace que iNO sea una variable libre. Aún así, consideramos que su función es un cierre, pero sin ninguna variable libre, ese es el punto.
Leemes
2
@leemes, estoy de acuerdo. Y en comparación con la respuesta aceptada, esto realmente no muestra lo que realmente está sucediendo. :)
Abel
3
Creo que esta es la mejor respuesta, explicando los cierres en general y de manera simple y luego entró en el caso de uso específico. ¡Gracias!
Tim Peterson
22

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

function outerFunc(){
    var outerVar = 'an outerFunc var';
    return function(){
        alert(outerVar);
    }
}

outerFunc()(); //returns inner function and fires it

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.

function outerFunc(){
    var incrementMe = 0;
    return function(){ incrementMe++; console.log(incrementMe); }
}
var inc = outerFunc();
inc(); //logs 1
inc(); //logs 2
Erik Reppen
fuente
Tienes razón sobre la 'instantánea' (creo que te refieres a mi respuesta) por eso. Estaba buscando una palabra que indicara el comportamiento. En su ejemplo, puede verse como una construcción de cierre 'hotlink'. Al capturar el cierre como parámetro en la función interna, se podría afirmar que se comporta como una 'instantánea'. Pero estoy de acuerdo, las palabras mal utilizadas solo agregan confusión al tema. Si tiene alguna sugerencia al respecto, actualizaré mi respuesta.
Andries
Podría ayudar en la explicación si le das a la función interna una función con nombre.
Phillip Senn
Sin cierres, obtendría un error porque está intentando utilizar una variable que no existe.
Juan Mendes
Hmm ... buen punto. ¿Hacer referencia a una variable indefinida nunca arrojó un error, ya que finalmente se vería como una propiedad en el objeto global o estoy confundiendo con la asignación a variables indefinidas?
Erik Reppen
17

Ambos están usando cierres.

Voy con la definición de Wikipedia aquí:

En informática, un cierre (también cierre léxico o cierre de 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 puntero de función simple, permite que una función acceda a esas variables no locales incluso cuando se invoca fuera de su ámbito léxico inmediato.

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 local i2.

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 misma i2. Como el interior de la función anónima interna i2no es local, esto crea un cierre.

Jon
fuente
Sí, pero creo que el punto es cómo lo está haciendo. Él sólo copias ia i2, 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.
Leemes
66
@leemes: Capturó imuy 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 pasarla icomo argumento (que copia su valor actual en el acto). Si pones el tuyo setTimeoutdentro de otro setTimeout, sucederá lo mismo.
Jon
13

Tanto usted como su 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: https://developer.mozilla.org/en-US/docs/JavaScript/Guide/Closures

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

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

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

Otro equivalente (pero con menos utilización de memoria) es:

function fGenerator(i2){
    return function(){
        console.log(i2);
    }
}
for(var i = 0; i < 10; i++) {
    setTimeout(fGenerator(i), 1000);
}
Andrew D.
fuente
1
No veo por qué su solución frente a la solución de mi amigo "es más rápida y con menos utilización de memoria", ¿podría dar más detalles?
brillante
3
En su solución, crea 20 objetos de función (2 objetos en cada bucle: 2x10 = 20). Mismo resultado en la solución de su frend. En "mi" solución, solo se crean 11 objetos de función: 1 antes para el bucle y 10 "dentro" - 1 + 1x10 = 11. Como resultado, menor uso de memoria y aumento de velocidad.
Andrew D.
1
En teoría, eso sería cierto. En la práctica, también: Vea este punto de referencia JSPerf
Rob W
10

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:

// 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 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!

Andries
fuente
10

Veamos las dos formas:

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

Declara e inmediatamente ejecuta una función anónima que se ejecuta setTimeout()dentro de su propio contexto. El valor actual de ise conserva haciendo una copia en i2primer lugar; Funciona debido a 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 ise conserva el valor actual de 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 se transfiere a setTimeout()mientras que su función interna se llama a setTimeout()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.

Jack
fuente
Creo que la diferencia es: su solución (primera) es capturar por referencia, la mía (segunda) es capturar por valor. En este caso, no hay diferencia, pero si tuviera que poner la ejecución en otro setTimeout, veríamos que su solución tiene el problema de que luego usa el valor final de i, no el actual, mientras que el umbral de la mina usa el valor actual (ya que capturado por valor).
Leemes
@leemes Ambos capturan de la misma manera; pasar una variable mediante un argumento de función o una asignación es lo mismo ... ¿podría agregar a su pregunta cómo envolvería la ejecución en otra setTimeout()?
Ja͢ck
permítanme verificar esto ... Quería mostrar que el objeto de la función se puede pasar y la variable original ise puede cambiar sin afectar lo que la función debería imprimir, sin depender de dónde o cuándo lo ejecutamos.
Leemes
Espere, no pasó una función a (el exterior) setTimeout. Elimínelos (), pasando así una función, y verá 10 veces la salida 10.
Leemes
@leemes Como se mencionó anteriormente, esto ()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.
Ja͢ck
8

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.

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

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

En el caso de sus amigos, ise accede dentro de la función anónima 1 y i2se accede en la función anónima 2 donde console.logestá presente.

En su caso, está accediendo i2dentro de la función anónima donde console.logestá presente. Agregue una debugger;declaración antes console.logy en las herramientas de desarrollador de Chrome en "Variables de alcance" que le dirá bajo qué alcance es la variable.

Ramesh
fuente
2
La sección "Cierre" en el panel derecho se usa porque no hay un nombre más específico. "Local" es una indicación más fuerte que "Cierre".
Rob W
4

Considera lo siguiente. ¡Esto crea y recrea una función fque se cierra 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ícito:

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

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

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

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

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

ekim
fuente
fragmentos procesados por primera vez - no estoy seguro de cómo controlar - Run' only was desired - not sure how to remove the Copy`
ekim
-1

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.

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!')

El resultado de este código sería el siguiente:

*****      hello      #####

      good bye!    ♥♥♥

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.

Eunjung Lee
fuente
Me gustaría eliminar esta respuesta. Me di cuenta de que la pregunta no es sobre qué es el cierre, por lo que me gustaría pasar a la otra pregunta.
Eunjung Lee
2
Creo que tienes la capacidad de eliminar tu propio contenido. Haz clic en el deleteenlace debajo de la respuesta.
Rory McCrossan