Una chiusura è una funzione e l'ambito di tale funzione.
Aiuta a capire come Javascript implementa lo scope in questo caso. È, infatti, solo una serie di dizionari nidificati. Considerate questo codice:
var global1 = "foo";
function myFunc() {
var x = 0;
global1 = "bar";
}
myFunc();
All'avvio del programma in esecuzione, si dispone di un unico dizionario ambito, il dizionario globale, che potrebbe avere un certo numero di cose definite in esso:
{ global1: "foo", myFunc:<function code> }
Dire si chiama myFunc , che ha una variabile locale x. Viene creato un nuovo ambito per l'esecuzione di questa funzione. L'ambito locale della funzione è simile al seguente:
{ x: 0 }
Contiene anche un riferimento all'ambito principale. Quindi l'intero scopo della funzione è il seguente:
{ x: 0, parentScope: { global1: "foo", myFunc:<function code> } }
Ciò consente a myFunc di modificare global1. In Javascript, ogni volta che si tenta di assegnare un valore a una variabile, prima controlla l'ambito locale per il nome della variabile. Se non viene trovato, controlla genitoreScope e genitoreScope di tale scope, ecc. Fino a quando la variabile non viene trovata.
Una chiusura è letteralmente una funzione più un puntatore all'ambito di questa funzione (che contiene un puntatore all'ambito principale e così via). Quindi, nel tuo esempio, dopo il ciclo for
ha terminato l'esecuzione, la portata potrebbe essere simile a questo:
setupHelpScope = {
helpText:<...>,
i: 3,
item: {'id': 'age', 'help': 'Your age (you must be over 16)'},
parentScope: <...>
}
Ogni chiusura di creare punterà a questo singolo oggetto ambito. Se dovessimo elencare ogni chiusura che si è creato, che sarebbe simile a questa:
[anonymousFunction1, setupHelpScope]
[anonymousFunction2, setupHelpScope]
[anonymousFunction3, setupHelpScope]
Quando una di queste funzioni viene eseguito, si utilizza l'oggetto ambito che è stata approvata - in questo caso, è la stessa portata oggetto per ogni funzione! Ognuno guarderà la stessa variabile item
e vedrà lo stesso valore, che è l'ultimo impostato dal ciclo for
.
Per rispondere alla tua domanda, non importa se aggiungi var item
sopra il ciclo for
o al suo interno. Poiché i loop for
non creano il proprio ambito, item
verrà archiviato nel dizionario dell'ambito della funzione corrente, ovvero setupHelpScope
. Gli involucri generati all'interno del ciclo for
punteranno sempre a setupHelpScope
.
Alcune note importanti:
- Questo comportamento si verifica perché, in Javascript,
for
loop non hanno un proprio campo d'applicazione - che basta usare la portata della funzione di inclusione. Questo vale anche per if
, while
, switch
, ecc. Se si trattasse di C#, d'altra parte, un nuovo oggetto di ambito verrà creato per ciascun ciclo e ogni chiusura conterrà un puntatore al proprio ambito univoco.
- Si noti che se
anonymousFunction1
modifica una variabile nel proprio ambito, modifica quella variabile per le altre funzioni anonime. Questo può portare ad alcune interazioni davvero bizzarre.
- Gli ambiti sono solo oggetti, come quelli con cui si programma. Specificamente, sono dizionari. La macchina virtuale JS gestisce la loro cancellazione dalla memoria proprio come qualsiasi altra cosa - con il garbage collector. Per questo motivo, l'uso eccessivo di chiusure può creare un reale rigonfiamento della memoria. Poiché una chiusura contiene un puntatore a un oggetto ambito (che a sua volta contiene un puntatore al suo oggetto ambito genitore e avanti e indietro), l'intera catena dell'ambito non può essere raccolta da garbage collection e deve rimanere in memoria.
Ulteriori approfondimenti:
javascript ha 'let'? cercare ... – Kobi
@Kobi: È un'estensione specifica di Mozilla per JavaScript. Vedi [questo] (https://developer.mozilla.org/en/new_in_javascript_1.7). –
Se è specifico per Mozilla, vuol dire che dovrei evitare di usarlo? – Nick