2010-06-30 4 views
8

Sto usando jQuery e ho una cosa strana che non capisco. Ho qualche codice:Strange cose in JavaScript "per"

for (i = 1; i <= some_number; i++) { 
    $("#some_button" + i).click(function() { 
     alert(i); 
    }); 
} 

"#some_button", come dice il nome - sono alcuni pulsanti. Quando cliccato dovrebbero apparire una casella con il suo numero, corretto? Ma loro non lo fanno. Se sono presenti 4 pulsanti, pop-up sempre "5" (i pulsanti contano + 1). Perchè è così?

+0

È un problema molto comune come suggeriscono la maggior parte delle risposte. Inoltre, tutte le domande contrassegnate da 'javascript',' chiusure 'e 'cicli' punteranno a questo problema e soluzioni esatte - [javascript + chiusure + cicli] (http://stackoverflow.com/questions/tagged/javascript+closeures+ loop), incluso questo ora :) – Anurag

+0

abbiamo bisogno di un modo per SO di avere un enorme pop-up che dice "QUESTO È STATO RISPOSTO PRIMA", con collegamenti alla domanda giusta. Ho visto almeno 3 versioni di JavaScript e 2 versioni di Python che fanno questa domanda. – Claudiu

+1

@Claudiu: Sfortunatamente è molto difficile per i principianti JavaScript/Python cercare questi tipi di problemi. –

risposta

18

Ha a che fare con lo scoping di JavaScript. Si può aggirare facilmente con l'introduzione di un altro ambito con l'aggiunta di una funzione e avere quella funzione stessa chiamata e passare in i:

for (var i = 1; i <= some_number; i++) { 
    (function(j) { 
    $("#some_button" + j).click(function() { 
     alert(j); 
    }); 
    })(i); 
} 

questo modo si crea una chiusura - quando la funzione interna ha accesso a un ambito che non esiste più quando viene chiamata la funzione. Vedere this article su MDC per ulteriori informazioni.

MODIFICA: RE: Funzioni di auto-chiamata: una funzione di auto-chiamata è una funzione che si chiama in modo anonimo. Non lo si istanzia e non lo si assegna a una variabile. Esso ha la seguente forma (notare le parentesi di apertura):

(function(args) { 
    // function body that might modify args 
})(args_to_pass_in); 

Relativamente questo alla domanda, il corpo della funzione anonima sarebbe:

$("#some_button" + j).click(function() { 
    alert(j); 
}); 

Combinando questi insieme, si ottiene la risposta in il primo blocco di codice. La funzione di auto-chiamata anonima si aspetta un argomento chiamato j. Cerca qualsiasi elemento con un id di some_button con il valore intero di j alla fine (ad esempio some_button1, some_button10). Ogni volta che si fa clic su uno di questi elementi, viene segnalato il valore di j. La penultima riga della soluzione passa nel valore i, che è il contatore di loop in cui viene chiamata la funzione di auto-chiamata anonima. Fatto un altro modo, potrebbe assomigliare a questo:

var innerFunction = function(j) { 
    $("#some_button" + j).click(function() { 
    alert(j); 
    }); 
}; 

for (var i = 1; i <= some_number; i++) { 
    innerFunction(i); 
} 
+0

caro dio :( Per favore, per favore, riscrivilo in un modo più leggibile? Nessuno che non so già che la risposta capirà mai una cosa dal pezzo che hai scritto.))}}) (i);} ... –

+1

@SF: questo è praticamente quello che devi fare .. Forse questo aiuterà: si attiva 'for (...) {LOGIC (i); } ', dove' LOGIC' è un codice arbitrario che funziona sulla variabile 'i', in' for (...) {(FUNC) (i)} ', creando immediatamente una funzione e dandogli' i'. 'FUNC' è semplicemente' function (j) {LOGIC (j); } '. – Claudiu

+0

Sì, puoi pensare a una chiusura come una funzione che ha un riferimento a ogni variabile esterna che usa, piuttosto che una copia. Questo bug è sorto perché sono stato usato come riferimento all'interno della chiusura. Questa correzione la risolve copiando il valore in j - passandolo in una funzione come argomento. –

9

Si stanno avendo a very common closure problem nel ciclo for.

Le variabili racchiuse in una chiusura condividono lo stesso singolo ambiente, quindi quando viene chiamato il callback click, il ciclo avrà corso e la variabile i rimarrà puntata all'ultima voce.

si può risolvere questo con ancora più chiusure, utilizzando una funzione di fabbrica:

function makeOnClickCallback(i) { 
    return function() { 
     alert(i); 
    }; 
} 

var i; 

for (i = 0; i < some_number; i++) { 
    $("#some_button" + i).click(makeOnClickCallback(i)); 
} 

Questo può essere un bel argomento difficile, se non si ha familiarità con il modo chiusure funzionano. Si può verificare il seguente articolo di Mozilla per una breve introduzione:

-1
(function (some_number) { 
    for (i = 1; i <= some_number; i++) { 
     $("#some_button" + i).click(function() { 
     alert(i); 
     }); 
    } 
    })(some_number); 

Avvolgere la funzione di fuori, perché per la velocità e il fatto che io manterrò il ripristino.

+3

Sei sicuro di aver completato la parte giusta? – HoLyVieR

2

Perché nel momento in cui si fa clic su di loro, i == 5.

+0

heh molto succinta, ma questo aiuterà solo qualcuno che già sa qual è il problema – Claudiu

+0

Whoa, ha appena ottenuto un upvote 6 mesi più tardi .. comunque quello che l'OP ha davvero bisogno è di studiare il concetto di "chiusure", quindi non di più misteri. –

0

Questo è a causa di come funzionano le chiusure in JavaScript.Ciascuna delle 5 funzioni che stai creando condivide fondamentalmente la stessa variabile i. Il valore della i all'interno della vostra funzione non è in corso di valutazione quando si crea la funzione, ma quando si verifica l'evento click, quando ormai il valore di i è 5.

Ci sono varie tecniche per ottenere intorno a questo (quando questo il comportamento non è quello che vuoi). Uno (se si dispone di una funzione semplice, come si fa qui) è quello di utilizzare il costruttore funzione invece di una funzione letterale:

$("#some_button" + i).click(new Function("alert("+i+")"); 
-1

Questo è un codice molto intelligente. Così intelligente è una domanda su SO. :) Allontanerei la domanda del tutto abbassando il codice, solo per avere la possibilità di capirlo (o di averlo capito da un collega) tra sei mesi. Le chiusure hanno il loro posto, ma in questo caso le eviterei a favore di un codice più comprensibile.

Probabilmente, collegherei la stessa funzione a tutti i pulsanti, che otterrebbero il pulsante dall'evento, rimuovere "some_button" dall'ID e avvisare il risultato. Non così grazioso, ma garantisco che tutti in ufficio possano seguirlo a colpo d'occhio.

+0

È meglio avere un codice complesso per aggirare un problema o evitare del tutto il problema? –