Si prega di spiegare l’uso delle chiusure JavaScript nei loop

Ho letto una serie di spiegazioni su chiusure e chiusure all’interno di anelli. Ho difficoltà a capire il concetto. Ho questo codice: c’è un modo per ridurre il più ansible il codice in modo da rendere più chiaro il concetto di chiusura. Sto avendo difficoltà a capire la parte in cui l’ i trova all’interno di due parentesi. Grazie

 function addLinks () { for (var i=0, link; i<5; i++) { link = document.createElement("a"); link.innerHTML = "Link " + i; link.onclick = function (num) { return function () { alert(num); }; }(i); document.body.appendChild(link); } } window.onload = addLinks; 

ATTENZIONE: risposta lunga (ish)

Questo viene copiato direttamente da un articolo che ho scritto in una wiki interna all’azienda:

Domanda: come utilizzare correttamente le chiusure nei cappi? Risposta rapida: utilizzare una fabbrica di funzioni.

  for (var i=0; i<10; i++) { document.getElementById(i).onclick = (function(x){ return function(){ alert(x); } })(i); } 

o la versione più facilmente leggibile:

  function generateMyHandler (x) { return function(){ alert(x); } } for (var i=0; i<10; i++) { document.getElementById(i).onclick = generateMyHandler(i); } 

Questo spesso confonde le persone che sono nuove nella programmazione javascript o funzionale. È il risultato di incomprensioni su chi sono le chiusure.

Una chiusura non passa semplicemente il valore di una variabile o anche un riferimento alla variabile. Una chiusura cattura la variabile stessa! Il seguente bit di codice illustra questo:

  var message = 'Hello!'; document.getElementById('foo').onclick = function(){alert(message)}; message = 'Goodbye!'; 

Facendo clic sull'elemento "pippo" verrà generata una finestra di avviso con il messaggio: "Arrivederci!". Per questo motivo, l'utilizzo di una chiusura semplice in un ciclo terminerà con tutte le chiusure che condividono la stessa variabile e quella variabile conterrà l'ultimo valore assegnato ad esso nel ciclo. Per esempio:

  for (var i=0; i<10; i++) { document.getElementById('something'+i).onclick = function(){alert(i)}; } 

Tutti gli elementi quando cliccati genereranno una casella di avviso con il numero 10. Infatti, se ora facciamo i="hello"; tutti gli elementi genereranno ora un avviso "ciao"! La variabile i è condivisa su dieci funzioni PIÙ l'attuale funzione / ambito / contesto. Pensala come una sorta di variabile globale privata che solo le funzioni coinvolte possono vedere.

Quello che vogliamo è un'istanza di quella variabile o almeno un semplice riferimento alla variabile invece della variabile stessa. Fortunatamente javascript ha già un meccanismo per passare un riferimento (per oggetti) o un valore (per stringhe e numeri): argomenti della funzione!

Quando una funzione viene chiamata in javascript, gli argomenti a tale funzione vengono passati per riferimento se si tratta di un object o di un valore se si tratta di una stringa o di un numero. Questo è sufficiente per interrompere la condivisione variabile delle chiusure.

Così:

  for (var i=0; i<10; i++) { document.getElementById(i).onclick = (function(x){ /* we use this function expression simply as a factory to return the function we really want to use: */ /* we want to return a function reference so we write a function expression*/ return function(){ alert(x); /* x here refers to the argument of the factory function captured by the 'inner' closure */ } /* The brace operators (..) evaluates an expression, in this case this function expression which yields a function reference. */ })(i) /* The function reference generated is then immediately called() where the variable i is passed */ } 

Ho programmato in JavaScript per molto tempo e la “chiusura in loop” è un argomento molto ampio. Presumo che tu stia parlando della pratica dell’uso (function(param) { return function(){ ... }; })(param); all’interno di un ciclo for per preservare il “valore corrente” del loop quando quella funzione interna viene eseguita successivamente …

Il codice:

 for(var i=0; i<4; i++) { setTimeout( // argument #1 to setTimeout is a function. // this "outer function" is immediately executed, with `i` as its parameter (function(x) { // the "outer function" returns an "inner function" which now has x=i at the // time the "outer function" was called return function() { console.log("i=="+i+", x=="+x); }; })(i) // execute the "closure" immediately, x=i, returns a "callback" function // finishing up arguments to setTimeout , i*100); } 

Produzione:

 i==4, x==0 i==4, x==1 i==4, x==2 i==4, x==3 

Come puoi vedere dall'output, tutte le funzioni di callback interne puntano tutte allo stesso i , tuttavia, poiché ognuna ha la sua "chiusura", il valore di x viene effettivamente memorizzato come qualsiasi cosa ero al momento della funzione esterna esecuzione.

Comunemente quando vedi questo modello, dovresti usare lo stesso nome di variabile del parametro e l'argomento della funzione esterna: (function(i){ })(i) per esempio. Qualsiasi codice all'interno di quella funzione (anche se eseguita successivamente, come una funzione di callback) si riferirà a i nel momento in cui hai chiamato la "funzione esterna".

Bene, il “problema” con le chiusure in questo caso è che qualsiasi accesso a i farebbe riferimento alla stessa variabile. Ciò è dovuto ECMA-/Javascripts della funzione ECMA-/Javascripts o lexical scope .

Quindi, per evitare che ogni chiamata in alert(i); visualizzerebbe un 5 (perché dopo che il ciclo ha terminato i === 5), è necessario creare una nuova funzione che si invochi da sola al runtime.

Per raggiungere questo objective, è necessario creare una nuova funzione, oltre alla necessità di aggiungere la parata aggiuntiva alla fine, per invoke the outer function immediatamente la invoke the outer function , quindi link.onclick ha ora la funzione restituita come riferimento.

Una chiusura è un costrutto in cui si fa riferimento a una variabile al di fuori dell’ambito in cui è definita. Di solito parli di chiusure nel contesto di una funzione.

 var helloFunction; var finished = false; while (!finished) { var message = 'Hello, World!'; helloFunction = function() { alert(message); } finished = true; } helloFunction(); 

Qui, definisco il messaggio variabile e definisco una funzione che fa riferimento al messaggio . Quando definisco la funzione per usare il messaggio, sto creando una chiusura. Ciò significa che helloFunction conserva un riferimento al messaggio , in modo che possa continuare a utilizzare il messaggio , anche al di fuori dell’ambito (il corpo del ciclo) in cui è definito il messaggio .

appendice

Il (i) tra parentesi è una chiamata di funzione. Quello che sta succedendo è:

  1. Si definisce una funzione (num) {}. Questa è chiamata funzione anonima , perché è definita in linea e non ha un nome.
  2. function (num) accetta un argomento intero e restituisce un riferimento a un’altra funzione, definita come alert (num)
  3. La funzione anonima esterna viene immediatamente chiamata, con l’argomento i . Quindi num = i . Il risultato di questa chiamata è una funzione che avviserà (i).
  4. Il risultato finale è più o meno equivalente a: link.onclick = function() { alert(i); }; link.onclick = function() { alert(i); };

Per rispondere all’ultima parte delle tue domande. Le due parentesi invocano la funzione come qualsiasi altra funzione. Perché lo fai qui è che vuoi mantenere quello che la variabile “i” è proprio in quel momento. Quindi quello che fa è invocare la funzione, l’i viene inviato come argomento “num”. Dal momento che è invocato, ricorderà il valore nume nei link delle variabili proprio scoop.

Se non lo facessi, tutti i link clic risulterebbero in un avviso che diceva “5”

John Resig, fondatore di jQuery, ha una bella presentazione online che spiega questo. http://ejohn.org/apps/learn/

..fredrik