Funzione crea eserciti
Il seguente codice crea un array di shooters
.
Ogni funzione è pensata per ritornare il numero, Ma qualcosa non va…
function makeArmy() {
let shooters = [];
let i = 0;
while (i < 10) {
let shooter = function() { // shooter function
alert( i ); // should show its number
};
shooters.push(shooter);
i++;
}
return shooters;
}
let army = makeArmy();
army[0](); // the shooter number 0 shows 10
army[5](); // and number 5 also outputs 10...
// ... all shooters show 10 instead of their 0, 1, 2, 3...
Perché tutti gli eserciti possiedono lo stesso numero di militari? Modificate il codice in modo tale che funzioni correttamente.
Esaminiamo cosa accade dentro makeArmy
, e la soluzioni ci apparirà ovvia.
-
Crea un array vuoto
shooters
:let shooters = [];
-
Lo riempie con un ciclo
shooters.push(function...)
.Ogni elemento è una funzione, quindi l’array finale risulterà essere:
shooters = [ function () { alert(i); }, function () { alert(i); }, function () { alert(i); }, function () { alert(i); }, function () { alert(i); }, function () { alert(i); }, function () { alert(i); }, function () { alert(i); }, function () { alert(i); }, function () { alert(i); } ];
-
L’array viene ritornato dalla funzione.
Successivamente, la chiamata army[5]()
recupererà l’elemento army[5]
dall’array (cioè una funzione) e la invocherà.
Ora perché tutte le funzione mostrano lo stesso risultato, 10
?
Questo accade perché non c’è alcuna variabile locale i
interna alla funzione shooter
. Quando questa funzione viene invocata, prende i
dal lexical environment esterno.
Quale sarà il valore di i
?
Se guardiamo il codice:
function makeArmy() {
...
let i = 0;
while (i < 10) {
let shooter = function() { // shooter function
alert( i ); // dovrebbe mostrare il suo numero
};
shooters.push(shooter); // aggiunge function all'array
i++;
}
...
}
Possiamo notare che la funzione shooter
viene creata nel lexical environment di makeArmy()
. Ma quando invochiamo army[5]()
, makeArmy
ha già terminato l’esecuzione, ed il valore finale di i
è 10
(while
si ferma a i=10
).
Il risultato è che tutte le funzioni shooter
prendono lo stesso valore dal lexical envrironment esterno, in cui l’ultimo valore è i=10
.
Come puoi vedere qui sotto, a ogni iterazione del blocco while {...}
viene creato un nuovo lexical environment. Quindi, per correggere, possiamo copiare il valore di i
in una variabile all’interno del blocco while {...}
stesso, così:
function makeArmy() {
let shooters = [];
let i = 0;
while (i < 10) {
let j = i;
let shooter = function() { // shooter function
alert( j ); // dovrebbe mostrare il suo numero
};
shooters.push(shooter);
i++;
}
return shooters;
}
let army = makeArmy();
// Ora il codice funziona correttamente
army[0](); // 0
army[5](); // 5
Qui let j = i
dichiara la variabile j
“locale all’iterazione” e copia i
al suo interno. I tipi primitivi vengo copiato “per valore”, quindi otteniamo una copia indipendente di i
, appartenente all’iterazione corrente del ciclo.
Shooters funziona correttamente, perché il valore di i
è un po’ più vicino. Non è nel Lexical Environment di makeArmy()
, ma nel Lexical Environment che corrisponde all’iterazione corrente del ciclo:
Questo tipo di problema può anche essere evitato usando for
all’inizio, in questo modo:
function makeArmy() {
let shooters = [];
for(let i = 0; i < 10; i++) {
let shooter = function() { // shooter function
alert( i ); // dovrebbe mostrare il suo numero
};
shooters.push(shooter);
}
return shooters;
}
let army = makeArmy();
army[0](); // 0
army[5](); // 5
Essenzialmente è la stessa cosa, perché ad ogni iterazione for
viene generato un nuovo lexical environment, con la propria variabile i
. Quindi shooter
generato in ogni iterazione fa riferimento alla propria i
, in quella stessa iterazione.