var funcs = [];
// let's create 3 functions
for (var i = 0; i < 3; i++) {
// and store them in funcs
funcs[i] = function() {
// each should log its value.
console.log("My value: " + i);
};
}
for (var j = 0; j < 3; j++) {
// and now let's run each one to see
funcs[j]();
}
Produce esto:
Mi valor: 3
Mi valor: 3
Mi valor: 3
Mientras que me gustaría que salga:
Mi valor: 0
Mi valor: 1
Mi valor: 2
El mismo problema se produce cuando el retraso en la ejecución de la función se debe al uso de escuchas de eventos:
var buttons = document.getElementsByTagName("button");
// let's create 3 functions
for (var i = 0; i < buttons.length; i++) {
// as event listeners
buttons[i].addEventListener("click", function() {
// each should log its value.
console.log("My value: " + i);
});
}
<button>0</button>
<br />
<button>1</button>
<br />
<button>2</button>
... o código asincrónico, por ejemplo, usando Promesas:
// Some async wait function
const wait = (ms) => new Promise((resolve, reject) => setTimeout(resolve, ms));
for (var i = 0; i < 3; i++) {
// Log `i` as soon as each promise resolves.
wait(i * 100).then(() => console.log(i));
}
Editar: También es evidente en for iny for ofbucles:
const arr = [1,2,3];
const fns = [];
for(i in arr){
fns.push(() => console.log(`index: ${i}`));
}
for(v of arr){
fns.push(() => console.log(`value: ${v}`));
}
for(f of fns){
f();
}
¿Cuál es la solución a este problema básico?
javascript
loops
closures
nickf
fuente
fuente

funcsser una matriz, si estás usando índices numéricos? Solo un aviso.Respuestas:
Bueno, el problema es que la variable
i, dentro de cada una de sus funciones anónimas, está vinculada a la misma variable fuera de la función.Solución clásica: cierres
Lo que desea hacer es vincular la variable dentro de cada función a un valor separado e inmutable fuera de la función:
Dado que no hay un alcance de bloque en JavaScript, solo el alcance de la función, al envolver la creación de la función en una nueva función, se asegura de que el valor de "i" permanezca como lo deseaba.
Solución ES5.1: para cada uno
Con la disponibilidad relativamente generalizada de la
Array.prototype.forEachfunción (en 2015), vale la pena señalar que en aquellas situaciones que involucran la iteración principalmente sobre una matriz de valores,.forEach()proporciona una forma limpia y natural de obtener un cierre distinto para cada iteración. Es decir, suponiendo que tiene algún tipo de matriz que contiene valores (referencias DOM, objetos, lo que sea), y surge el problema de configurar devoluciones de llamada específicas para cada elemento, puede hacer esto:La idea es que cada invocación de la función de devolución de llamada utilizada con el
.forEachbucle será su propio cierre. El parámetro pasado a ese controlador es el elemento de matriz específico para ese paso particular de la iteración. Si se usa en una devolución de llamada asincrónica, no colisionará con ninguna de las otras devoluciones de llamada establecidas en otros pasos de la iteración.Si está trabajando en jQuery, la
$.each()función le brinda una capacidad similar.Solución ES6:
letECMAScript 6 (ES6) presenta palabras clave nuevas
lety conconstun alcance diferente alvarde las variables basadas. Por ejemplo, en un bucle con unletíndice basado en, cada iteración a través del bucle tendrá una nueva variableicon alcance de bucle, por lo que su código funcionará como espera. Hay muchos recursos, pero recomendaría la publicación de alcance de bloque de 2ality como una gran fuente de información.Sin embargo, tenga en cuenta que IE9-IE11 y Edge antes de Edge 14 admiten
letpero se equivocan en lo anterior (no crean una nuevaicada vez, por lo que todas las funciones anteriores registrarían 3 como lo harían si las usáramosvar). Edge 14 finalmente lo hace bien.fuente
function createfunc(i) { return function() { console.log("My value: " + i); }; }Todavía no se cierra porque usa la variablei?Function.bind()definitivamente es preferible ahora, consulte stackoverflow.com/a/19323214/785541 ..bind()es "la respuesta correcta" no es correcta. Cada uno tiene su propio lugar. Con.bind()usted no puede vincular argumentos sin vincular elthisvalor. También obtienes una copia deliargumento sin la capacidad de mutarlo entre llamadas, lo que a veces es necesario. Por lo tanto, son construcciones bastante diferentes, sin mencionar que las.bind()implementaciones han sido históricamente lentas. Claro, en el ejemplo simple, cualquiera funcionaría, pero los cierres son un concepto importante para entender, y de eso se trataba la pregunta.Tratar:
Editar (2014):
Personalmente, creo que la respuesta más reciente de
.bind@ Aust sobre el uso es la mejor manera de hacer este tipo de cosas ahora. Hay también lo-dash / subrayado es_.partialcuando no se necesita o quiere meterse conbind'sthisArg.fuente
}(i));?icomo argumentoindexde la función.Otra forma que aún no se ha mencionado es el uso de
Function.prototype.bindACTUALIZAR
Como lo señalaron @squint y @mekdev, obtienes un mejor rendimiento creando primero la función fuera del ciclo y luego vinculando los resultados dentro del ciclo.
fuente
_.partial.bind()será obsoleto en gran medida con las características de ECMAScript 6. Además, esto realmente crea dos funciones por iteración. Primero el anónimo, luego el generado por.bind(). Mejor uso sería crearlo fuera del ciclo, luego.bind()dentro.bindse usa. He agregado otro ejemplo por sus sugerencias.this.Usando una expresión de función invocada inmediatamente , la forma más simple y fácil de encerrar una variable de índice:
Esto envía el iterador
ia la función anónima que definimos comoindex. Esto crea un cierre, donde la variableise guarda para su uso posterior en cualquier funcionalidad asincrónica dentro del IIFE.fuente
ies qué, cambiaría el nombre del parámetro de funciónindex.indexlugar dei.var funcs = {}; for (var i = 0; i < 3; i++) { funcs[i] = (function(index) { return function() {console.log('iterator: ' + index);}; })(i); }; for (var j = 0; j < 3; j++) { funcs[j](); }.forEach(), pero una gran parte del tiempo, cuando uno está empezando con una matriz,forEach()es una opción buena, como:var nums [4, 6, 7]; var funcs = {}; nums.forEach(function (num, i) { funcs[i] = function () { console.log(num); }; });Llegué un poco tarde a la fiesta, pero hoy estaba explorando este problema y noté que muchas de las respuestas no abordan completamente cómo Javascript trata los ámbitos, que es esencialmente a lo que se reduce todo esto.
Entonces, como muchos otros mencionaron, el problema es que la función interna hace referencia a la misma
ivariable. Entonces, ¿por qué no creamos una nueva variable local en cada iteración, y tenemos la función interna de referencia?Al igual que antes, donde cada función interna generaba el último valor asignado
i, ahora cada función interna solo genera el último valor asignadoilocal. ¿Pero no debería cada iteración tener la suya propiailocal?Resulta que ese es el problema. Cada iteración comparte el mismo alcance, por lo que cada iteración después de la primera solo se sobrescribe
ilocal. De MDN :Reiterado por énfasis:
Podemos ver esto comprobando
ilocalantes de declararlo en cada iteración:Esto es exactamente por qué este error es tan complicado. Aunque esté redeclarando una variable, Javascript no arrojará un error y JSLint ni siquiera lanzará una advertencia. Esta es también la razón por la cual la mejor manera de resolver esto es aprovechar los cierres, que es esencialmente la idea de que en Javascript, las funciones internas tienen acceso a variables externas porque los ámbitos internos "encierran" ámbitos externos.
Esto también significa que las funciones internas "mantienen" las variables externas y las mantienen vivas, incluso si la función externa regresa. Para utilizar esto, creamos y llamamos a una función de contenedor únicamente para crear un nuevo ámbito, declarar
ilocalen el nuevo ámbito y devolver una función interna que utilizailocal(más explicación a continuación):La creación de la función interna dentro de una función contenedora le da a la función interna un entorno privado al que solo puede acceder, un "cierre". Por lo tanto, cada vez que llamamos a la función de contenedor, creamos una nueva función interna con su propio entorno separado, asegurando que las
ilocalvariables no colisionen y se sobrescriban entre sí. Algunas optimizaciones menores dan la respuesta final que muchos otros usuarios de SO dieron:Actualizar
Con ES6 ahora convencional, ahora podemos usar la nueva
letpalabra clave para crear variables de ámbito de bloque:¡Mira lo fácil que es ahora! Para obtener más información, consulte esta respuesta , en la que se basa mi información.
fuente
letyconst. Si esta respuesta se ampliara para incluir eso, sería mucho más útil a nivel mundial en mi opinión.letyi=0; while(i < 100) { setTimeout(function(){ window.open("https://www.bbc.com","_self") }, 3000); setTimeout(function(){ window.open("https://www.cnn.com","_self") }, 3000); i++ }:? (podría reemplazar window.open () con getelementbyid ......)isus funciones de tiempo de espera, por lo que no necesita un cierreCon ES6 ahora ampliamente admitido, la mejor respuesta a esta pregunta ha cambiado. ES6 proporciona las palabras clave
letyconstpara esta circunstancia exacta. En lugar de perder el tiempo con los cierres, solo podemos usarletpara establecer una variable de alcance de bucle como esta:valluego apuntará a un objeto que sea específico para ese giro particular del bucle y devolverá el valor correcto sin la notación de cierre adicional. Obviamente, esto simplifica significativamente este problema.constes similar aletla restricción adicional de que el nombre de la variable no puede ser devuelto a una nueva referencia después de la asignación inicial.El soporte del navegador ahora está aquí para aquellos que se dirigen a las últimas versiones de los navegadores.
const/letactualmente son compatibles con los últimos Firefox, Safari, Edge y Chrome. También es compatible con Node, y puede usarlo en cualquier lugar aprovechando herramientas de compilación como Babel. Puede ver un ejemplo de trabajo aquí: http://jsfiddle.net/ben336/rbU4t/2/Documentos aquí:
Sin embargo, tenga en cuenta que IE9-IE11 y Edge antes de Edge 14 admiten
letpero se equivocan en lo anterior (no crean una nuevaicada vez, por lo que todas las funciones anteriores registrarían 3 como lo harían si las usáramosvar). Edge 14 finalmente lo hace bien.fuente
Otra forma de decirlo es que el
ien su función está vinculado al momento de ejecutar la función, no al momento de crear la función.Cuando creas el cierre,
ies una referencia a la variable definida en el ámbito externo, no una copia de la misma como era cuando creó el cierre. Será evaluado en el momento de la ejecución.La mayoría de las otras respuestas proporcionan formas de evitarlo creando otra variable que no cambiará el valor por usted.
Solo pensé en agregar una explicación para mayor claridad. Para una solución, personalmente, iría con la de Harto, ya que es la forma más autoexplicativa de hacerlo a partir de las respuestas aquí. Cualquiera de los códigos publicados funcionará, pero optaría por una fábrica de cierre en lugar de tener que escribir un montón de comentarios para explicar por qué estoy declarando una nueva variable (Freddy y 1800) o tengo una sintaxis de cierre incrustada extraña (apphacker).
fuente
Lo que debe comprender es que el alcance de las variables en JavaScript se basa en la función. Esta es una diferencia importante que decir c # donde tiene un alcance de bloque, y simplemente copiando la variable a una dentro de for funcionará.
Ajustarlo en una función que evalúa devolver la función como la respuesta de apphacker hará el truco, ya que la variable ahora tiene el alcance de la función.
También hay una palabra clave let en lugar de var, que permitiría usar la regla de alcance de bloque. En ese caso, definir una variable dentro de for haría el truco. Dicho esto, la palabra clave let no es una solución práctica debido a la compatibilidad.
fuente
Aquí hay otra variación de la técnica, similar a la de Bjorn (apphacker), que le permite asignar el valor de la variable dentro de la función en lugar de pasarlo como un parámetro, que a veces puede ser más claro:
Tenga en cuenta que, sea cual sea la técnica que utilice, la
indexvariable se convierte en una especie de variable estática, vinculada a la copia devuelta de la función interna. Es decir, los cambios en su valor se conservan entre llamadas. Puede ser muy útil.fuente
varlínea y lareturnlínea no funcionaría. ¡Gracias!varyreturnluego la variable no se asignaría antes de devolver la función interna.Esto describe el error común al usar cierres en JavaScript.
Una función define un nuevo entorno.
Considerar:
Por cada vez que
makeCounterse invoca,{counter: 0}se crea un nuevo objeto. Además,objse crea una nueva copia de para hacer referencia al nuevo objeto. Por lo tanto,counter1ycounter2son independientes entre sí.Cierres en bucles
Usar un cierre en un bucle es complicado.
Considerar:
Tenga en cuenta que
counters[0]ycounters[1]son no independientes. De hecho, ¡operan de la misma maneraobj!Esto se debe a que solo hay una copia de
objcompartida en todas las iteraciones del bucle, tal vez por razones de rendimiento. Aunque{counter: 0}crea un nuevo objeto en cada iteración, la misma copiaobjse actualizará con una referencia al objeto más nuevo.La solución es usar otra función auxiliar:
Esto funciona porque las variables locales en el ámbito de la función directamente, así como las variables de argumento de la función, se asignan nuevas copias al ingresar.
fuente
var obj = {counter: 0};se evalúa antes de que se ejecute cualquier código como se indica en: MDN var : las declaraciones de var, donde sea que ocurran, se procesan antes de que se ejecute cualquier código.La solución más simple sería,
En lugar de usar:
que alerta "2", por 3 veces. Esto se debe a que las funciones anónimas creadas en for loop, comparten el mismo cierre, y en ese cierre, el valor de
ies el mismo. Use esto para evitar el cierre compartido:La idea detrás de esto es encapsular todo el cuerpo del bucle for con una IIFE (Expresión de función invocada inmediatamente) y pasarla
new_icomo un parámetro y capturarlo comoi. Como la función anónima se ejecuta inmediatamente, elivalor es diferente para cada función definida dentro de la función anónima.Esta solución parece ajustarse a cualquier problema, ya que requerirá cambios mínimos en el código original que sufre este problema. De hecho, esto es por diseño, ¡no debería ser un problema en absoluto!
fuente
prueba este más corto
sin matriz
sin extra para loop
http://jsfiddle.net/7P6EN/
fuente
Aquí hay una solución simple que usa
forEach(vuelve a IE9):Huellas dactilares:
fuente
El principal problema con el código que muestra el OP es que
inunca se lee hasta el segundo bucle. Para demostrarlo, imagine ver un error dentro del códigoEl error en realidad no ocurre hasta que
funcs[someIndex]se ejecuta(). Usando esta misma lógica, debería ser evidente que el valor deitampoco se recopila hasta este punto tampoco. Una vez que finaliza el ciclo original,i++llevaial valor de lo3cual resulta en lai < 3falla de la condición y el final del ciclo. En este punto,ies3y cuandofuncs[someIndex]()se usa, yise evalúa, es 3, cada vez.Para superar esto, debe evaluar
icómo se encuentra. Tenga en cuenta que esto ya ha sucedido en forma defuncs[i](donde hay 3 índices únicos). Hay varias formas de capturar este valor. Una es pasarlo como parámetro a una función que ya se muestra de varias maneras aquí.Otra opción es construir un objeto de función que pueda cerrarse sobre la variable. Eso se puede lograr así
jsFiddle Demofuente
Las funciones de JavaScript "cierran" el alcance al que tienen acceso tras la declaración, y retienen el acceso a ese alcance incluso cuando cambian las variables en ese alcance.
Cada función en la matriz anterior se cierra sobre el alcance global (global, simplemente porque ese es el alcance en el que se declaran).
Más tarde, esas funciones se invocan registrando el valor más actual de
ien el ámbito global. Esa es la magia y la frustración del cierre."Las funciones de JavaScript se cierran sobre el alcance en el que se declaran y retienen el acceso a ese alcance incluso cuando cambian los valores variables dentro de ese alcance".
El uso en
letlugar devarresuelve esto creando un nuevo alcance cada vez que seforejecuta el bucle, creando un alcance separado para cada función para cerrar. Varias otras técnicas hacen lo mismo con funciones adicionales.(
lethace que las variables tengan un alcance de bloque. Los bloques se indican mediante llaves, pero en el caso del bucle for, la variable de inicialización,ien nuestro caso, se considera declarada en las llaves).fuente
ise establece en el ámbito global. Cuando elforciclo termina de ejecutarse, el valor global deiahora es 3. Por lo tanto, cada vez que se invoca esa función en la matriz (usando, por ejemplofuncs[j]), laifunción in hace referencia a laivariable global (que es 3).Después de leer varias soluciones, me gustaría agregar que la razón por la que esas soluciones funcionan es confiar en el concepto de cadena de alcance . Es la forma en que JavaScript resuelve una variable durante la ejecución.
vary susarguments.window.En el código inicial:
Cuando
funcsse ejecuta, la cadena de alcance seráfunction inner -> global. Como la variableino se puede encontrar enfunction inner(ni declarada usandovarni pasada como argumentos), continúa buscando, hasta que el valor deifinalmente se encuentra en el ámbito global que eswindow.i.Al envolverlo en una función externa, defina explícitamente una función auxiliar como lo hizo harto o use una función anónima como lo hizo Bjorn :
Cuando
funcsse ejecuta, ahora la cadena de alcance seráfunction inner -> function outer. Este tiempoise puede encontrar en el alcance de la función externa que se ejecuta 3 veces en el ciclo for, cada vez tiene un valorienlazado correctamente. No usará el valor dewindow.icuando se ejecute internamente.Se pueden encontrar más detalles aquí.
Incluye el error común al crear un cierre en el ciclo como lo que tenemos aquí, así como por qué necesitamos el cierre y la consideración del rendimiento.
fuente
Array.prototype.forEach(function callback(el) {})funcionan de manera natural otras formas `` modernas '' : la devolución de llamada que se pasa naturalmente forma el alcance envolvente con el correctamente enlazado en cada iteración deforEach. Por lo tanto, cada función interna definida en la devolución de llamada podrá usar elelvalor correctoCon las nuevas características de ES6 se gestiona el alcance del nivel de bloque:
El código en la pregunta de OP se reemplaza con en
letlugar devar.fuente
constproporciona el mismo resultado y debe usarse cuando el valor de una variable no cambiará. Sin embargo, el uso deconstdentro del inicializador del bucle for se implementa incorrectamente en Firefox y aún no se ha corregido. En lugar de ser declarado dentro del bloque, se declara fuera del bloque, lo que resulta en una redeclaración de la variable, lo que a su vez resulta en un error. El uso delletinterior del inicializador se implementa correctamente en Firefox, por lo que no debe preocuparse allí.Me sorprende que nadie haya sugerido usar la
forEachfunción para evitar (re) usar mejor las variables locales. De hecho, ya no estoy usandofor(var i ...)nada por esta razón.// editado para usar en
forEachlugar de mapa.fuente
.forEach()es una opción mucho mejor si en realidad no está mapeando nada, y Daryl sugirió que 7 meses antes de publicar, por lo que no hay nada de qué sorprenderse.¡Esta pregunta realmente muestra la historia de JavaScript! Ahora podemos evitar el alcance del bloque con funciones de flecha y manejar bucles directamente desde nodos DOM utilizando métodos Object.
fuente
La razón por la que su ejemplo original no funcionó es porque todos los cierres que creó en el bucle hacían referencia al mismo marco. En efecto, tener 3 métodos en un objeto con una sola
ivariable. Todos imprimieron el mismo valor.fuente
En primer lugar, entienda lo que está mal con este código:
Aquí, cuando la
funcs[]matriz se inicializa,ise incrementa, lafuncsmatriz se inicializa y el tamaño de lafuncmatriz se convierte en 3, entoncesi = 3,. Ahora cuandofuncs[j]()se llama al, nuevamente está usando la variablei, que ya se ha incrementado a 3.Ahora para resolver esto, tenemos muchas opciones. A continuación hay dos de ellos:
Podemos inicializar
iconleto inicializar una nueva variableindexconlety que tenga la mismai. Entonces, cuando se realiza la llamada,indexse utilizará y su alcance finalizará después de la inicialización. Y para llamar,indexse inicializará nuevamente:Otra opción puede ser introducir una
tempFuncque devuelva la función real:fuente
Utilice la estructura de cierre , esto reduciría su bucle adicional. Puedes hacerlo en un solo ciclo for:
fuente
Caso1 : usando
varAhora abra la ventana de la consola de Chrome presionando F12 y actualice la página. Gasta cada 3 funciones dentro de la matriz. Verás una propiedad llamada.
[[Scopes]]Expande esa. Verá un objeto de matriz llamado"Global", expanda ese. Encontrará una propiedad'i'declarada en el objeto que tiene valor 3.Conclusión:
'var'fuera de una función, se convierte en una variable global (puede verificar escribiendoiowindow.ien la ventana de la consola, devolverá 3).console.log("My value: " + i)toma el valor de suGlobalobjeto y muestra el resultado.CASO2: usando let
Ahora reemplace el
'var'con'let'Haz lo mismo, ve a los ámbitos. Ahora verá dos objetos
"Block"y"Global". Ahora expanda elBlockobjeto, verá que 'i' está definido allí, y lo extraño es que, para cada función, el valor ifies diferente (0, 1, 2).Conclusión:
Cuando declara una variable usando
'let'incluso fuera de la función pero dentro del ciclo, esta variable no será una variable Global, se convertirá en unaBlockvariable de nivel que solo está disponible para la misma función. Esa es la razón, estamos obteniendo el valor deidiferentes para cada función cuando invocamos las funciones.Para obtener más detalles sobre cómo funciona más de cerca, consulte el increíble video tutorial https://youtu.be/71AtaJpJHw0
fuente
Puede usar un módulo declarativo para listas de datos como query-js (*). En estas situaciones personalmente encuentro menos sorprendente un enfoque declarativo
Luego, podría usar su segundo ciclo y obtener el resultado esperado o podría hacer
(*) Soy el autor de query-js y por lo tanto estoy predispuesto a usarlo, así que no tome mis palabras como una recomendación para dicha biblioteca solo para el enfoque declarativo :)
fuente
Query.range(0,3)? Esto no es parte de las etiquetas para esta pregunta. Además, si utiliza una biblioteca de terceros, puede proporcionar el enlace de la documentación.Prefiero usar la
forEachfunción, que tiene su propio cierre con la creación de un pseudo rango:Eso parece más feo que los rangos en otros idiomas, pero en mi humilde opinión, menos monstruoso que otras soluciones.
fuente
Y otra solución más: en lugar de crear otro bucle, simplemente vincula la
thisfunción de retorno.Al vincular esto , también se resuelve el problema.
fuente
Muchas soluciones parecen correctas, pero no mencionan que se llama,
Curryingque es un patrón de diseño de programación funcional para situaciones como esta. De 3 a 10 veces más rápido que el enlace, dependiendo del navegador.Vea la ganancia de rendimiento en diferentes navegadores .
fuente
Su código no funciona, porque lo que hace es:
Ahora la pregunta es, ¿cuál es el valor de la variable
icuando se llama a la función? Debido a que el primer bucle se crea con la condición dei < 3, se detiene inmediatamente cuando la condición es falsa, por lo que es asíi = 3.Debe comprender que, cuando se crean sus funciones, ninguno de sus códigos se ejecuta, solo se guarda para más adelante. Entonces, cuando se les llama más tarde, el intérprete los ejecuta y pregunta: "¿Cuál es el valor actual de
i?"Por lo tanto, su objetivo es guardar primero el valor de
ifuncionar y solo después guardar la función enfuncs. Esto podría hacerse, por ejemplo, de esta manera:De esta forma, cada función tendrá su propia variable
xy estableceremos estoxen el valor deien cada iteración.Esta es solo una de las múltiples formas de resolver este problema.
fuente
fuente
Use let (bloqueado-alcance) en lugar de var.
fuente