2010-06-01 19 views
10

Sto leggendo "Javascript: The Good Parts" e sono totalmente sconcertato da quello che sta succedendo qui. Una spiegazione più dettagliata e/o semplificata sarebbe molto apprezzata.Chiusure: spiegazione riga per riga dell'esempio "Javascript: parti buone"?

// BAD EXAMPLE 

// Make a function that assigns event handler functions to an array of nodes the wrong way. 
// When you click on a node, an alert box is supposed to display the ordinal of the node. 
// But it always displays the number of nodes instead. 

var add_the_handlers = function (nodes) { 
    var i; 
    for (i = 0; i < nodes.length; i += 1) { 
     nodes[i].onclick = function (e) { 
      alert(i); 
     } 
    } 
}; 

// END BAD EXAMPLE 

La funzione add_the_handlers aveva lo scopo di dare ad ogni gestore di un numero univoco (i). Esso non è riuscito perché le funzioni di gestione sono legati alla variabile i, non il valore della variabile i al momento è stata fatta la funzione:

// BETTER EXAMPLE 

// Make a function that assigns event handler functions to an array of nodes the right way. 
// When you click on a node, an alert box will display the ordinal of the node. 

var add_the_handlers = function (nodes) { 
    var i; 
    for (i = 0; i < nodes.length; i += 1) { 
     nodes[i].onclick = function (i) { 
      return function (e) { 
       alert(i); 
      }; 
     }(i); 
    } 
}; 

Ora, invece di assegnare una funzione a onclick, si definisce una funzione e invocarlo immediatamente, passando i. Questa funzione restituirà una funzione di gestore eventi associata al valore di i passato, non allo i definito in add_the_handlers. Quella funzione restituita è assegnata a onclick.

+0

Vedi le domande Tagged in: http://stackoverflow.com/questions/tagged/javascript+closures+loops – CMS

+0

È può anche giocare con una demo dal vivo http://jsbin.com/sezisalulede/1/edit?html,js,output –

risposta

20

Penso che questa sia una fonte molto comune di confusione per i principianti di JavaScript. In primo luogo vorrei suggerire check-out il seguente articolo di Mozilla Dev per breve introduzione sul tema delle chiusure e scoping lessicale:

Cominciamo con il cattivo:

var add_the_handlers = function (nodes) { 
// Variable i is declared in the local scope of the add_the_handlers() 
// function. 
    var i; 

// Nothing special here. A normal for loop. 
    for (i = 0; i < nodes.length; i += 1) { 

// Now we are going to assign an anonymous function to the onclick property. 
     nodes[i].onclick = function (e) { 

// The problem here is that this anonymous function has become a closure. It 
// will be sharing the same local variable environment as the add_the_handlers() 
// function. Therefore when the callback is called, the i variable will contain 
// the last value it had when add_the_handlers() last returned. 
      alert(i); 
     } 
    } 

// The for loop ends, and i === nodes.length. The add_the_handlers() maintains 
// the value of i even after it returns. This is why when the callback 
// function is invoked, it will always alert the value of nodes.length. 
}; 

Possiamo affrontare questo problema con più chiusure, come suggerito da Crockford nel "buon esempio". Una chiusura è un tipo speciale di oggetto che combina due cose: una funzione e l'ambiente in cui è stata creata quella funzione. In JavaScript, l'ambiente della chiusura è costituito da tutte le variabili locali che erano in-scope nel momento in cui la chiusura è stato creato:

// Now we are creating an anonymous closure that creates its own local 
// environment. I renamed the parameter variable x to make it more clear. 
nodes[i].onclick = function (x) { 

    // Variable x will be initialized when this function is called. 

    // Return the event callback function. 
    return function (e) { 
     // We use the local variable from the closure environment, and not the 
     // one held in the scope of the outer function add_the_handlers(). 
     alert(x); 
    }; 
}(i); // We invoke the function immediately to initialize its internal 
     // environment that will be captured in the closure, and to receive 
     // the callback function which we need to assign to the onclick. 

Piuttosto che avere i callback tutti condividono un unico ambiente, la funzione di chiusura crea un nuovo ambiente per ognuno. Potremmo anche usato una fabbrica funzione per creare una chiusura, come nel seguente esempio:

function makeOnClickCallback (x) { 
    return function (e) { 
     alert(x); 
    }; 
} 

for (i = 0; i < nodes.length; i += 1) { 
    nodes[i].onclick = makeOnClickCallback(i); 
} 
+0

Una domanda in qualche modo correlata. Cosa significa "e" nella funzione (e) e potrebbe essere sostituito con qualsiasi var? Pensavo che significasse evento, ma ora sono confuso. – Matrym

+0

@Matrym: Sì, dovrebbe essere un argomento che il browser passa alla funzione di callback quando viene generato l'evento onclick. Controlla [questo articolo quirksmode] (http://www.quirksmode.org/js/events_access.html) su come questo viene gestito in diversi browser. –

+0

Cosa succede se non usiamo una variabile? Passiamo così possiamo mettere insieme le cose? – Matrym

3

Si tratta di chiusure. Nel primo esempio, "i" sarà uguale a "nodes.length" per ogni gestore di eventi di clic, poiché utilizza "i" dal ciclo che crea i gestori di eventi. Nel momento in cui viene chiamato il gestore di eventi, il ciclo sarà terminato, quindi "i" sarà uguale a "nodes.length".

Nel secondo esempio, "i" è un parametro (quindi una variabile locale). I gestori di eventi useranno il valore della variabile locale "i" (il parametro).

0

Ha a che fare con la chiusura.

Quando si esegue la cosa al cattivo esempio,

quando si fa clic su ogni nodo, si otterrà il valore i Ultima (vale a dire si dispone di 3 nodi, non importa quale nodo si fa clic si ottengono 2). poiché il tuo avviso (i) è associato a un riferimento di variabile i e non il valore di i al momento in cui è stato associato al gestore di eventi.

Facendo il modo migliore di esempio, è stato associato a ciò che ho nel momento in cui è stato iterato, quindi facendo clic sul nodo 1 si otterrà 0, il nodo 2 darà 1 e il nodo 3 fornirà 2

in pratica, si sta valutando ciò che è immediatamente quando viene chiamato sulla linea} (i) e si è passati al parametro e che ora detengono il valore di ciò che è in quel momento nel tempo.

Btw ... Penso che ci sia un refuso nella parte migliore dell'esempio ... dovrebbe essere alert (e) invece di alert (i).

2

In entrambi gli esempi, qualsiasi nodo passato ha un gestore di eventi onclick associato ad esso (proprio come <img src="..." onclick="myhandler()"/>, che dopotutto è una cattiva pratica).

La differenza è che nel cattivo esempio ogni chiusura (il gestore di eventi funziona, cioè) fa riferimento esattamente alla stessa variabile i a causa del loro ambito genitore comune.

Il buon esempio si avvale di una funzione anonima che viene eseguita immediatamente. Questa funzione anonima fa riferimento alla stessa variabile i come nell'esempio negativo MA poiché viene eseguita e fornita con i come primo parametro, il valore di i viene assegnato a una variabile locale denominata ... eh? ... i, esattamente - sovrascrivendo quindi quello definito nell'ambito del genitore.

Riscriviamo il buon esempio per rendere il tutto chiaro:

var add_the_handlers = function (nodes) { 
    var i; 
    for (i = 0; i < nodes.length; i += 1) { 
     nodes[i].onclick = function (newvar) { 
      return function (e) { 
       alert(nevar); 
      }; 
     }(i); 
    } 
}; 

Qui abbiamo sostituito i nella funzione di gestore di eventi tornato con newvar e funziona ancora, perché newvar è proprio quello che ci si aspetta - un nuova variabile ereditata dall'ambito della funzione anonima.

Buona fortuna a comprenderlo.

Problemi correlati