loops continue - Chiusura JavaScript all'interno di loop-semplice esempio pratico




guida mr (25)

var funcs = [];
for (var i = 0; i < 3; i++) {      // let's create 3 functions
  funcs[i] = function() {          // and store them in funcs
    console.log("My value: " + i); // each should log its value.
  };
}
for (var j = 0; j < 3; j++) {
  funcs[j]();                      // and now let's run each one to see
}

Emette questo:

Il mio valore: 3
Il mio valore: 3
Il mio valore: 3

Mentre mi piacerebbe che uscisse:

Il mio valore: 0
Il mio valore: 1
Il mio valore: 2

Lo stesso problema si verifica quando il ritardo nell'esecuzione della funzione è causato dall'utilizzo di listener di eventi:

var buttons = document.getElementsByTagName("button");
for (var i = 0; i < buttons.length; i++) {          // let's create 3 functions
  buttons[i].addEventListener("click", function() { // as event listeners
    console.log("My value: " + i);                  // each should log its value.
  });
}
<button>0</button><br>
<button>1</button><br>
<button>2</button>

... o codice asincrono, ad esempio usando Promises:

// Some async wait function
const wait = (ms) => new Promise((resolve, reject) => setTimeout(resolve, ms));

for(var i = 0; i < 3; i++){
  wait(i * 100).then(() => console.log(i)); // Log `i` as soon as each promise resolves.
}

Qual è la soluzione a questo problema di base?


Answers

Molte soluzioni sembrano corrette ma non menzionano il suo nome, Curryingche è un modello di progettazione di programmazione funzionale per situazioni come qui. 3-10 volte più veloce del binding a seconda del browser.

var funcs = [];
for (var i = 0; i < 3; i++) {      // let's create 3 functions
  funcs[i] = curryShowValue(i);
}
for (var j = 0; j < 3; j++) {
  funcs[j]();                      // and now let's run each one to see
}

function curryShowValue(i) {
  return function showValue() {
    console.log("My value: " + i);
  }
}

Vedi il guadagno di prestazioni in diversi browser .


Con ES6 ora ampiamente supportato, la migliore risposta a questa domanda è cambiata. ES6 fornisce le parole chiave let e const per questa esatta circostanza. Invece di scherzare con le chiusure, possiamo semplicemente usare let per impostare una variabile del ciclo di variazione come questa:

var funcs = [];
for (let i = 0; i < 3; i++) {          
    funcs[i] = function() {            
      console.log("My value: " + i); 
    };
}

val indicherà quindi un oggetto specifico per quel particolare giro del ciclo e restituirà il valore corretto senza la notazione di chiusura aggiuntiva. Questo ovviamente semplifica notevolmente questo problema.

const è simile a let con la restrizione aggiuntiva che il nome della variabile non può essere rimbalzato su un nuovo riferimento dopo l'assegnazione iniziale.

Il supporto del browser è ora disponibile per coloro che scelgono come target le ultime versioni dei browser. const / let sono attualmente supportati negli ultimi Firefox, Safari, Edge e Chrome. Inoltre è supportato in Nodo, e puoi usarlo ovunque sfruttando strumenti di costruzione come Babel. Puoi vedere un esempio di lavoro qui: http://jsfiddle.net/ben336/rbU4t/2/

Docs qui:

Fate attenzione, tuttavia, che IE9-IE11 e Edge precedenti al supporto per Edge 14 let ottenere il suddetto errore (non creano un nuovo i ogni volta, quindi tutte le funzioni precedenti registreranno 3 come se fossero usate var ). Edge 14 finalmente ha ragione.


La soluzione più semplice sarebbe,

Invece di usare:

var funcs = [];
for(var i =0; i<3; i++){
    funcs[i] = function(){
        alert(i);
    }
}

for(var j =0; j<3; j++){
    funcs[j]();
}

che avvisa "2", per 3 volte. Questo perché le funzioni anonime create in ciclo for, condividono la stessa chiusura, e in quella chiusura il valore di i è lo stesso. Usalo per prevenire la chiusura condivisa:

var funcs = [];
for(var new_i =0; new_i<3; new_i++){
    (function(i){
        funcs[i] = function(){
            alert(i);
        }
    })(new_i);
}

for(var j =0; j<3; j++){
    funcs[j]();
}

L'idea alla base di questo è, incapsulare l'intero corpo del ciclo for con un IIFE (espressione di funzione immediatamente IIFE ) e passare new_i come parametro e catturarlo come i . Poiché la funzione anonima viene eseguita immediatamente, il valore i è diverso per ogni funzione definita all'interno della funzione anonima.

Questa soluzione sembra adattarsi a qualsiasi problema di questo tipo poiché richiede modifiche minime al codice originale che soffre di questo problema. In realtà, questo è di progettazione, non dovrebbe essere affatto un problema!


È possibile utilizzare un modulo dichiarativo per elenchi di dati come query-js (*). In queste situazioni personalmente trovo un approccio dichiarativo meno sorprendente

var funcs = Query.range(0,3).each(function(i){
     return  function() {
        console.log("My value: " + i);
    };
});

È quindi possibile utilizzare il secondo ciclo e ottenere il risultato previsto o si potrebbe fare

funcs.iterate(function(f){ f(); });

(*) Sono l'autore di query-js e quindi prevenuto sull'uso, quindi non prendere le mie parole come raccomandazione per tale libreria solo per l'approccio dichiarativo :)


Le funzioni JavaScript "chiudono" l'ambito al quale hanno accesso in base alla dichiarazione e conservano l'accesso a tale ambito anche se le variabili in quell'ambito cambiano.

var funcs = []

for (var i = 0; i < 3; i += 1) {
  funcs[i] = function () {
    console.log(i)
  }
}

for (var k = 0; k < 3; k += 1) {
  funcs[k]()
}

Ogni funzione nell'array sopra si chiude sull'ambito globale (globale, semplicemente perché questo è l'ambito in cui sono dichiarate).

Successivamente tali funzioni vengono richiamate registrando il valore più corrente di i nello scope globale. Questa è la magia e la frustrazione della chiusura.

"Le funzioni JavaScript si chiudono sull'ambito in cui sono dichiarate e conservano l'accesso a tale ambito anche se i valori delle variabili all'interno di quell'ambito cambiano."

L'utilizzo di let invece di var risolve questo creando un nuovo scope ogni volta che viene eseguito il ciclo for , creando un ambito separato per ogni funzione da chiudere. Varie altre tecniche fanno la stessa cosa con funzioni extra.

var funcs = []

for (let i = 0; i < 3; i += 1) {
  funcs[i] = function () {
    console.log(i)
  }
}

for (var k = 0; k < 3; k += 1) {
  funcs[k]()
}

( let rende variabili che sono scope scope invece di scope scope. I blocchi sono denotati da parentesi graffe, ma nel caso del ciclo for la variabile di inizializzazione, i nel nostro caso, è considerata dichiarata nelle parentesi graffe.)


Il tuo codice non funziona, perché quello che fa è:

Create variable `funcs` and assign it an empty array;  
Loop from 0 up until it is less than 3 and assign it to variable `i`;
    Push to variable `funcs` next function:  
        // Only push (save), but don't execute
        **Write to console current value of variable `i`;**

// First loop has ended, i = 3;

Loop from 0 up until it is less than 3 and assign it to variable `j`;
    Call `j`-th function from variable `funcs`:  
        **Write to console current value of variable `i`;**  
        // Ask yourself NOW! What is the value of i?

Ora la domanda è, qual è il valore della variabile iquando viene chiamata la funzione? Poiché il primo ciclo viene creato con la condizione di i < 3, si arresta immediatamente quando la condizione è falsa, così è i = 3.

Devi capire che, nel momento in cui vengono create le tue funzioni, nessuno dei loro codici viene eseguito, viene salvato solo per dopo. E così quando vengono chiamati in seguito, l'interprete li esegue e chiede: "Qual è il valore corrente di i?"

Quindi, il tuo obiettivo è di salvare prima il valore di ito function e solo dopo di ciò salvare la funzione in funcs. Questo potrebbe essere fatto ad esempio in questo modo:

var funcs = [];
for (var i = 0; i < 3; i++) {          // let's create 3 functions
    funcs[i] = function(x) {            // and store them in funcs
        console.log("My value: " + x); // each should log its value.
    }.bind(null, i);
}
for (var j = 0; j < 3; j++) {
    funcs[j]();                        // and now let's run each one to see
}

In questo modo, ciascuna funzione avrà la sua variabile xe la imposteremo xsul valore di iogni iterazione.

Questo è solo uno dei tanti modi per risolvere questo problema.


Quello che devi capire è che l'ambito delle variabili in javascript si basa sulla funzione. Questa è una differenza importante rispetto a c # in cui si dispone di un ambito di blocco e la copia della variabile in uno all'interno di for funzionerà.

Avvolgerlo in una funzione che valuta il ritorno della funzione come la risposta di Apphacker farà il trucco, poiché la variabile ora ha lo scopo della funzione.

C'è anche una parola chiave let invece di var, che permetterebbe di usare la regola dell'ambito del blocco. In quel caso la definizione di una variabile all'interno di per farebbe il trucco. Detto questo, la parola chiave let non è una soluzione pratica a causa della compatibilità.

var funcs = {};
for (var i = 0; i < 3; i++) {
    let index = i;          //add this
    funcs[i] = function() {            
        console.log("My value: " + index); //change to the copy
    };
}
for (var j = 0; j < 3; j++) {
    funcs[j]();                        
}

Utilizzo di un'espressione funzione richiamata immediatamente , il modo più semplice e leggibile per includere una variabile di indice:

for (var i = 0; i < 3; i++) {

    (function(index) {
        console.log('iterator: ' + index);
        //now you can also loop an ajax call here 
        //without losing track of the iterator value: $.ajax({});
    })(i);

}

Questo invia l'iteratore i alla funzione anonima di cui definiamo come index . Questo crea una chiusura, in cui la variabile viene salvata per un utilizzo successivo in qualsiasi funzionalità asincrona all'interno dell'IIFE.


Ecco una soluzione semplice che utilizza forEach (funziona di nuovo a IE9):

var funcs = [];
[0,1,2].forEach(function(i) {          // let's create 3 functions
    funcs[i] = function() {            // and store them in funcs
        console.log("My value: " + i); // each should log its value.
    };
})
for (var j = 0; j < 3; j++) {
    funcs[j]();                        // and now let's run each one to see
}

stampe:

My value: 0
My value: 1
My value: 2

Prima di tutto, capisci cosa c'è di sbagliato in questo codice:

var funcs = [];
for (var i = 0; i < 3; i++) {          // let's create 3 functions
    funcs[i] = function() {            // and store them in funcs
        console.log("My value: " + i); // each should log its value.
    };
}
for (var j = 0; j < 3; j++) {
    funcs[j]();                        // and now let's run each one to see
}

Qui, quando l' funcs[]array viene inizializzato, iviene incrementato, l' funcsarray viene inizializzato e la dimensione funcdell'array diventa 3, quindi i = 3,. Ora, quando funcs[j]()viene chiamato, utilizza nuovamente la variabile i, che è già stata incrementata a 3.

Ora per risolvere questo, abbiamo molte opzioni. Di seguito sono due di loro:

  1. Siamo in grado di inizializzare icon leto inizializzare una nuova variabile indexcon lete renderlo uguale i. Quindi, quando viene effettuata la chiamata, indexverrà utilizzato e il suo ambito terminerà dopo l'inizializzazione. E per la chiamata, indexsarà nuovamente inizializzato:

    var funcs = [];
    for (var i = 0; i < 3; i++) {          
        let index = i;
        funcs[i] = function() {            
            console.log("My value: " + index); 
        };
    }
    for (var j = 0; j < 3; j++) {
        funcs[j]();                        
    }
    
  2. L'altra opzione può essere quella di introdurre una tempFuncche restituisce la funzione effettiva:

    var funcs = [];
    function tempFunc(i){
        return function(){
            console.log("My value: " + i);
        };
    }
    for (var i = 0; i < 3; i++) {  
        funcs[i] = tempFunc(i);                                     
    }
    for (var j = 0; j < 3; j++) {
        funcs[j]();                        
    }
    

Controlleremo, cosa succede realmente quando dichiari vare letuno per uno.

Caso 1 : utilizzovar

<script>
   var funcs = [];
   for (var i = 0; i < 3; i++) {
     funcs[i] = function () {
        debugger;
        console.log("My value: " + i);
     };
   }
   console.log(funcs);
</script>

Ora apri la finestra della console di Chrome premendo F12 e aggiorna la pagina. Sposta ogni 3 funzioni all'interno dell'array. Vedrai una proprietà chiamata. [[Scopes]]Espande quella. Vedrai un oggetto array chiamato "Global", espandi quello. Troverai una proprietà 'i'dichiarata nell'oggetto che ha valore 3.

Conclusione:

  1. Quando dichiari una variabile usando 'var'una funzione esterna, diventa una variabile globale (puoi controllare digitando io window.inella finestra della console. Restituirà 3).
  2. La funzione annunziata che hai dichiarato non chiamerà e controllerà il valore all'interno della funzione a meno che tu non invochi le funzioni.
  3. Quando invochi la funzione, console.log("My value: " + i)prende il valore dal suo Globaloggetto e visualizza il risultato.

CASO2: usare let

Ora sostituire il 'var'con'let'

<script>
    var funcs = [];
    for (let i = 0; i < 3; i++) {
        funcs[i] = function () {
           debugger;
           console.log("My value: " + i);
        };
    }
    console.log(funcs);
</script>

Fai la stessa cosa, vai agli ambiti. Ora vedrai due oggetti "Block"e "Global". Ora espandi l' Blockoggetto, vedrai che "i" è definito lì, e la cosa strana è che, per ogni funzione, il valore iè diverso (0, 1, 2).

Conclusione:

Quando si dichiara la variabile usando 'let'anche al di fuori della funzione ma all'interno del ciclo, questa variabile non sarà una variabile Globale, diventerà una Blockvariabile di livello che è disponibile solo per la stessa funzione. Questo è il motivo, stiamo ottenendo valore di idiversi per ogni funzione quando invochiamo le funzioni.

Per maggiori dettagli su come funziona più da vicino, perfavore segui il fantastico video tutorial https://youtu.be/71AtaJpJHw0


COUNTER ESSERE UN PRIMITIVO

Definiamo le funzioni di callback come segue:

// ****************************
// COUNTER BEING A PRIMITIVE
// ****************************
function test1() {
    for (var i=0; i<2; i++) {
        setTimeout(function() {
            console.log(i);
        });
    }
}
test1();
// 2
// 2

Al termine del timeout verrà stampato 2 per entrambi. Questo perché la funzione di callback accede al valore in base all'ambito lessicale , in cui è stata definita la funzione.

Per passare e preservare il valore durante la definizione del callback, è possibile creare una closure , per preservare il valore prima che venga richiamato il callback. Questo può essere fatto come segue:

function test2() {
    function sendRequest(i) {
        setTimeout(function() {
            console.log(i);
        });
    }

    for (var i = 0; i < 2; i++) {
        sendRequest(i);
    }
}
test2();
// 1
// 2

Ora, ciò che rende speciale questo è "I primitivi vengono passati per valore e copiati, quindi quando la chiusura è definita, mantengono il valore dal ciclo precedente."

COUNTER ESSERE UN OGGETTO

Poiché le chiusure hanno accesso alle variabili di funzione genitore tramite riferimento, questo approccio sarebbe diverso da quello per le primitive.

// ****************************
// COUNTER BEING AN OBJECT
// ****************************
function test3() {
    var index = { i: 0 };
    for (index.i=0; index.i<2; index.i++) {
        setTimeout(function() {
            console.log('test3: ' + index.i);
        });
    }
}
test3();
// 2
// 2

Pertanto, anche se viene creata una chiusura per il passaggio della variabile come oggetto, il valore dell'indice del ciclo non verrà mantenuto. Questo per mostrare che i valori di un oggetto non vengono copiati mentre sono accessibili tramite riferimento.

function test4() {
    var index = { i: 0 };
    function sendRequest(index, i) {
        setTimeout(function() {
            console.log('index: ' + index);
            console.log('i: ' + i);
            console.log(index[i]);
        });
    }

    for (index.i=0; index.i<2; index.i++) {
        sendRequest(index, index.i);
    }
}
test4();
// index: { i: 2}
// 0
// undefined

// index: { i: 2}
// 1
// undefined

Questo descrive l'errore comune con l'utilizzo di chiusure in JavaScript.

Una funzione definisce un nuovo ambiente

Tenere conto:

function makeCounter()
{
  var obj = {counter: 0};
  return {
    inc: function(){obj.counter ++;},
    get: function(){return obj.counter;}
  };
}

counter1 = makeCounter();
counter2 = makeCounter();

counter1.inc();

alert(counter1.get()); // returns 1
alert(counter2.get()); // returns 0

Per ogni volta makeCounter viene richiamato makeCounter , {counter: 0} determina la creazione di un nuovo oggetto. Inoltre, viene creata una nuova copia di obj per fare riferimento al nuovo oggetto. Pertanto, counter1 e counter2 sono indipendenti l'uno dall'altro.

Chiusure a loops

Usare una chiusura in un loop è complicato.

Tenere conto:

var counters = [];

function makeCounters(num)
{
  for (var i = 0; i < num; i++)
  {
    var obj = {counter: 0};
    counters[i] = {
      inc: function(){obj.counter++;},
      get: function(){return obj.counter;}
    }; 
  }
}

makeCounters(2);

counters[0].inc();

alert(counters[0].get()); // returns 1
alert(counters[1].get()); // returns 1

Si noti che i counters[0] e i counters[1] non sono indipendenti. Infatti, operano sullo stesso obj !

Questo perché c'è una sola copia di obj condivisa tra tutte le iterazioni del ciclo, forse per motivi di prestazioni. Anche se {counter: 0} crea un nuovo oggetto in ogni iterazione, la stessa copia di obj verrà semplicemente aggiornata con un riferimento all'oggetto più recente.

La soluzione è usare un'altra funzione di supporto:

function makeHelper(obj)
{
  return {
    inc: function(){obj.counter++;},
    get: function(){return obj.counter;}
  }; 
}

function makeCounters(num)
{
  for (var i = 0; i < num; i++)
  {
    var obj = {counter: 0};
    counters[i] = makeHelper(obj);
  }
}

Questo funziona perché le variabili locali nello scope della funzione direttamente, così come le variabili degli argomenti della funzione, vengono allocate nuove copie all'entrata.

Per una discussione dettagliata, vedere i problemi di chiusura e l'utilizzo di JavaScript


Bene, il problema è che la variabile i , all'interno di ciascuna delle tue funzioni anonime, è legata alla stessa variabile al di fuori della funzione.

Soluzione classica: chiusure

Quello che vuoi fare è legare la variabile all'interno di ogni funzione a un valore separato, immutabile al di fuori della funzione:

var funcs = [];

function createfunc(i) {
    return function() { console.log("My value: " + i); };
}

for (var i = 0; i < 3; i++) {
    funcs[i] = createfunc(i);
}

for (var j = 0; j < 3; j++) {
    funcs[j]();                        // and now let's run each one to see
}

Poiché non esiste un ambito di blocco in JavaScript - solo ambito di funzione - avvolgendo la creazione della funzione in una nuova funzione, ci si assicura che il valore di "i" rimanga come desiderato.

Soluzione 2015: per tutti

Con la disponibilità relativamente estesa della funzione Array.prototype.forEach (nel 2015), vale la pena notare che in quelle situazioni che coinvolgono l'iterazione principalmente su una matrice di valori, .forEach() fornisce un modo pulito e naturale per ottenere una chiusura distinta per ogni iterazione. Cioè, supponendo che tu abbia una sorta di array contenente valori (riferimenti DOM, oggetti, qualunque cosa) e si pone il problema di impostare i callback specifici per ciascun elemento, puoi farlo:

var someArray = [ /* whatever */ ];
// ...
someArray.forEach(function(arrayElement) {
  // ... code code code for this one element
  someAsynchronousFunction(arrayElement, function() {
    arrayElement.doSomething();
  });
});

L'idea è che ogni invocazione della funzione di callback utilizzata con il ciclo .forEach sarà la sua chiusura. Il parametro passato a quel gestore è l'elemento dell'array specifico per quel particolare passaggio dell'iterazione. Se viene utilizzato in una richiamata asincrona, non si scontrerà con nessuno degli altri callback stabiliti in altri passaggi dell'iterazione.

Se lavori in jQuery, la funzione $.each() ti offre una capacità simile.

Soluzione ES6: let

ECMAScript 6 (ES6), la versione più recente di JavaScript, sta ora iniziando a essere implementata in molti browser e sistemi di backend sempreverdi. Ci sono anche transporter come Babel che convertono ES6 in ES5 per consentire l'utilizzo di nuove funzionalità su sistemi precedenti.

ES6 introduce nuove parole chiave let e const scope diverso dalle variabili var based. Ad esempio, in un ciclo con un indice basato su let , ogni iterazione attraverso il ciclo avrà un nuovo valore di i cui ogni valore è spaziato all'interno del ciclo, quindi il codice funzionerà come previsto. Ci sono molte risorse, ma raccomanderei il post di scoping a blocco di 2ality come una grande fonte di informazioni.

for (let i = 0; i < 3; i++) {
    funcs[i] = function() {
        console.log("My value: " + i);
    };
}

Fate attenzione, tuttavia, che IE9-IE11 e Edge precedenti al supporto per Edge 14 let ottenere il suddetto errore (non creano un nuovo i ogni volta, quindi tutte le funzioni precedenti registreranno 3 come se fossero usate var ). Edge 14 finalmente ha ragione.


Dopo aver letto varie soluzioni, vorrei aggiungere che il motivo per cui queste soluzioni funzionano è fare affidamento sul concetto di catena di portata . È il modo in cui JavaScript risolve una variabile durante l'esecuzione.

  • Ogni definizione di funzione forma un ambito costituito da tutte le variabili locali dichiarate da var e dai suoi arguments .
  • Se abbiamo una funzione interna definita all'interno di un'altra funzione (esterna), questa forma una catena e verrà utilizzata durante l'esecuzione
  • Quando una funzione viene eseguita, il runtime valuta le variabili effettuando una ricerca nella catena dell'ambito . Se una variabile può essere trovata in un certo punto della catena, smetterà di cercare e usarla, altrimenti continuerà fino a raggiungere l'ambito globale a cui appartiene window.

Nel codice iniziale:

funcs = {};
for (var i = 0; i < 3; i++) {         
  funcs[i] = function inner() {        // function inner's scope contains nothing
    console.log("My value: " + i);    
  };
}
console.log(window.i)                  // test value 'i', print 3

Quando funcsviene eseguito, la catena di portata sarà function inner -> global. Poiché la variabile inon può essere trovata in function inner(né dichiarata varné utilizzata né passata come argomenti), continua a cercare, fino a quando il valore di iè eventualmente trovato nello scope globale che è window.i.

share in una funzione esterna definisce esplicitamente una funzione di supporto come share did o usa una funzione anonima come ha fatto share :

funcs = {};
function outer(i) {              // function outer's scope contains 'i'
  return function inner() {      // function inner, closure created
   console.log("My value: " + i);
  };
}
for (var i = 0; i < 3; i++) {
  funcs[i] = outer(i);
}
console.log(window.i)          // print 3 still

Quando funcsviene eseguito, ora la catena di portata sarà function inner -> function outer. Questo tempo ipuò essere trovato nell'oscilloscopio della funzione esterna che viene eseguito 3 volte nel ciclo for, ogni volta che il valore è icorretto in modo corretto. Non userà il valore di window.iquando inner viene eseguito.

Maggiori dettagli possono essere trovati here
Include l'errore comune nel creare la chiusura nel ciclo come quello che abbiamo qui, così come il motivo per cui abbiamo bisogno di chiusura e considerazione delle prestazioni.


Sono sorpreso che nessuno abbia ancora suggerito di utilizzare la forEachfunzione per evitare (ri) utilizzare le variabili locali. In effetti, non sto più usando for(var i ...)per questo motivo.

[0,2,3].forEach(function(i){ console.log('My value:', i); });
// My value: 0
// My value: 2
// My value: 3

// modificato da utilizzare al forEachposto della mappa.


Il problema principale con il codice mostrato dall'OP è che non viene mai letto fino al secondo ciclo. Per dimostrare, immagina di vedere un errore all'interno del codice

funcs[i] = function() {            // and store them in funcs
    throw new Error("test");
    console.log("My value: " + i); // each should log its value.
};

L'errore in realtà non si verifica fino a quando funcs[someIndex] viene eseguito () . Usando questa stessa logica, dovrebbe essere evidente che anche il valore di i non viene raccolto fino a questo punto. Una volta che il ciclo originale termina, i++ porta i al valore di 3 che si traduce nella condizione in cui i < 3 fallisce e termina il ciclo. A questo punto, i è 3 e quindi quando viene usato funcs[someIndex]() , e i viene valutato, è 3 - ogni volta.

Per superare questo, è necessario valutare i come viene rilevato. Si noti che questo è già successo nella forma di funcs[i] (dove ci sono 3 indici unici). Esistono diversi modi per acquisire questo valore. Uno è passarlo come parametro a una funzione che è mostrata in diversi modi già qui.

Un'altra opzione è quella di costruire un oggetto funzione che sarà in grado di chiudere la variabile. Questo può essere realizzato in questo modo

jsFiddle Demo

funcs[i] = new function() {   
    var closedVariable = i;
    return function(){
        console.log("My value: " + closedVariable); 
    };
};

Preferisco usare la forEachfunzione, che ha la sua chiusura con la creazione di una pseudo gamma:

var funcs = [];

new Array(3).fill(0).forEach(function (_, i) { // creating a range
    funcs[i] = function() {            
        // now i is safely incapsulated 
        console.log("My value: " + i);
    };
});

for (var j = 0; j < 3; j++) {
    funcs[j](); // 0, 1, 2
}

Sembra più brutto delle gamme in altre lingue, ma IMHO è meno mostruoso di altre soluzioni.


Usa la struttura di closure , questo ridurrebbe il tuo ciclo extra. Puoi farlo in un ciclo for for:

var funcs = [];
for (var i = 0; i < 3; i++) {     
  (funcs[i] = function() {         
    console.log("My value: " + i); 
  })(i);
}

Un po 'tardi per la festa, ma stavo esplorando questo problema oggi e ho notato che molte delle risposte non affrontano completamente il modo in cui Javascript tratta gli ambiti, che è essenzialmente ciò a cui questo si riduce.

Così come molti altri hanno menzionato, il problema è che la funzione interna fa riferimento alla stessa variabile i . Quindi, perché non creiamo una nuova variabile locale ogni iterazione e abbiamo invece la funzione interna di riferimento?

//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};

var funcs = {};
for (var i = 0; i < 3; i++) {
    var ilocal = i; //create a new local variable
    funcs[i] = function() {
        console.log("My value: " + ilocal); //each should reference its own local variable
    };
}
for (var j = 0; j < 3; j++) {
    funcs[j]();
}

Proprio come prima, dove ogni funzione interna ha emesso l'ultimo valore assegnato a i , ora ogni funzione interna emette solo l'ultimo valore assegnato a ilocal . Ma non dovrebbe ogni iterazione avere il proprio ilocal ?

Risulta, questo è il problema. Ogni iterazione condivide lo stesso ambito, quindi ogni iterazione dopo la prima sta semplicemente sovrascrivendo ilocal . Da MDN :

Importante: JavaScript non ha scope di blocco. Le variabili introdotte con un blocco hanno come ambito la funzione o lo script di contenimento e gli effetti dell'impostazione persistono oltre il blocco stesso. In altre parole, le istruzioni di blocco non introducono un ambito. Anche se i blocchi "standalone" sono una sintassi valida, non si desidera utilizzare blocchi autonomi in JavaScript, perché non fanno ciò che si pensa di fare, se si pensa che facciano qualcosa come questi blocchi in C o Java.

Reiterato per enfasi:

JavaScript non ha scope di blocco. Le variabili introdotte con un blocco hanno come ambito la funzione o lo script di contenimento

Possiamo vederlo controllando ilocal prima di dichiararlo in ogni iterazione:

//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};

var funcs = {};
for (var i = 0; i < 3; i++) {
  console.log(ilocal);
  var ilocal = i;
}

Questo è esattamente il motivo per cui questo bug è così complicato. Anche se stai redecando una variabile, Javascript non genererà un errore e JSLint non invierà nemmeno un avviso. Questo è anche il motivo per cui il modo migliore per risolvere questo è sfruttare le chiusure, che è essenzialmente l'idea che in Javascript, le funzioni interne abbiano accesso alle variabili esterne perché gli ambiti interni "racchiudono" gli ambiti esterni.

Ciò significa anche che le funzioni interne "tengono" le variabili esterne e le mantengono in vita, anche se la funzione esterna ritorna. Per utilizzarlo, creiamo e chiamiamo una funzione wrapper puramente per creare un nuovo scope, dichiarare ilocal nel nuovo scope e restituire una funzione interna che usa ilocal (ulteriori spiegazioni sotto):

//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};

var funcs = {};
for (var i = 0; i < 3; i++) {
    funcs[i] = (function() { //create a new scope using a wrapper function
        var ilocal = i; //capture i into a local var
        return function() { //return the inner function
            console.log("My value: " + ilocal);
        };
    })(); //remember to run the wrapper function
}
for (var j = 0; j < 3; j++) {
    funcs[j]();
}

La creazione della funzione interna all'interno di una funzione wrapper conferisce alla funzione interiore un ambiente privato a cui solo esso può accedere, una "chiusura". Quindi, ogni volta che chiamiamo la funzione wrapper creiamo una nuova funzione interna con il suo ambiente separato, assicurando che le variabili ilocal non si ilocal e si sovrascrivano a vicenda. Alcune ottimizzazioni minori forniscono la risposta definitiva che molti altri utenti SO hanno dato:

//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};

var funcs = {};
for (var i = 0; i < 3; i++) {
    funcs[i] = wrapper(i);
}
for (var j = 0; j < 3; j++) {
    funcs[j]();
}
//creates a separate environment for the inner function
function wrapper(ilocal) {
    return function() { //return the inner function
        console.log("My value: " + ilocal);
    };
}

Aggiornare

Con ES6 ora mainstream, ora possiamo usare la nuova parola chiave let per creare variabili con scope a blocchi:

//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};

var funcs = {};
for (let i = 0; i < 3; i++) { // use "let" to declare "i"
    funcs[i] = function() {
        console.log("My value: " + i); //each should reference its own local variable
    };
}
for (var j = 0; j < 3; j++) { // we can use "var" here without issue
    funcs[j]();
}

Guarda com'è facile ora! Per maggiori informazioni vedi questa risposta , di cui sono basate le mie informazioni.


var funcs = [];
for (var i = 0; i < 3; i++) {      // let's create 3 functions
  funcs[i] = function(param) {          // and store them in funcs
    console.log("My value: " + param); // each should log its value.
  };
}
for (var j = 0; j < 3; j++) {
  funcs[j](j);                      // and now let's run each one to see with j
}

prova questo più corto

  • nessuna matrice

  • nessun extra per loop


for (var i = 0; i < 3; i++) {
    createfunc(i)();
}

function createfunc(i) {
    return function(){console.log("My value: " + i);};
}

http://jsfiddle.net/7P6EN/


Un altro modo che non è stato ancora menzionato è l'uso di Function.prototype.bind

var funcs = {};
for (var i = 0; i < 3; i++) {
  funcs[i] = function(x) {
    console.log('My value: ' + x);
  }.bind(this, i);
}
for (var j = 0; j < 3; j++) {
  funcs[j]();
}

AGGIORNARE

Come sottolineato da @squint e @mekdev, si ottengono prestazioni migliori creando prima la funzione all'esterno del ciclo e quindi vincolando i risultati all'interno del ciclo.

function log(x) {
  console.log('My value: ' + x);
}

var funcs = [];

for (var i = 0; i < 3; i++) {
  funcs[i] = log.bind(this, i);
}

for (var j = 0; j < 3; j++) {
  funcs[j]();
}


Ecco un'altra variante della tecnica, simile a Bjorn's (apphacker), che ti permette di assegnare il valore della variabile all'interno della funzione piuttosto che passarlo come parametro, che a volte potrebbe essere più chiaro:

for (var i = 0; i < 3; i++) {
    funcs[i] = (function() {
        var index = i;
        return function() {
            console.log("My value: " + index);
        }
    })();
}

Nota che qualunque sia la tecnica che usi, la variabile index diventa una sorta di variabile statica, legata alla copia restituita della funzione interna. Vale a dire, le modifiche al suo valore sono conservate tra le chiamate. Può essere molto utile.


Lo uso come

<tbody>
{ numrows ? (
   numrows.map(obj => { return <ObjectRow /> }) 
) : null}
</tbody>






javascript loops closures