Armée de fonctions
Le code suivant crée un tableau de shooters
.
Chaque fonction est censée sortir son numéro. Mais quelque chose ne va pas …
function makeArmy() {
let shooters = [];
let i = 0;
while (i < 10) {
let shooter = function() { // créer une fonction shooter
alert( i ); // qui devrait afficher son numéro
};
shooters.push(shooter); // and add it to the array
i++;
}
// ...and return the array of shooters
return shooters;
}
let army = makeArmy();
// tous les shooters affichent 10 au lieu de leurs numéros 0, 1, 2, 3 ...
army[0](); // 10 du shooter numéro 0
army[1](); // 10 du shooter numéro 1
army[2](); // 10 ...etc.
Pourquoi tous les shooters affichent-ils la même valeur ?
Corrigez le code pour qu’il fonctionne comme prévu.
Examinons exactement ce qui se fait à l’intérieur de makeArmy
, et la solution deviendra évidente.
-
Elle crée un tableau vide
shooters
:let shooters = [];
-
Le remplit avec des fonctions dans la boucle via
shooters.push(function)
.Chaque élément est une fonction, le tableau résultant ressemble à ceci :
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); } ];
-
Le tableau est renvoyé par la fonction.
Puis, plus tard, l’appel à n’importe quel membre, par ex.
Army[5]()
récupérera l’élémentarmy[5]
du tableau (qui est une fonction) et l’appellera.Maintenant, pourquoi toutes ces fonctions affichent-elles la même valeur,
10
?C’est parce qu’il n’y a pas de variable locale
i
dans les fonctions deshooter
. Lorsqu’une telle fonction est appelée, elle prendi
de son environnement lexical externe.Alors, quelle sera la valeur de
i
?Si nous regardons la source :
function makeArmy() { ... let i = 0; while (i < 10) { let shooter = function() { // fonction shooter alert( i ); // should show its number }; shooters.push(shooter); // ajoute une fonction au tableau i++; } ... }
Nous pouvons voir que toutes les fonctions
shooter
sont créées dans l’environnement lexical de la fonctionmakeArmy()
. Mais quandarmy[5]()
est appelé,makeArmy
a déjà terminé son travail, et la valeur finale dei
est10
(while
s’arrête ài=10
).En conséquence, toutes les fonctions
shooter
obtiennent la même valeur de l’environnement lexical externe et c’est-à-dire la dernière valeur,i=10
.Comme vous pouvez le voir ci-dessus, à chaque itération d’un bloc
while {...}
, un nouvel environnement lexical est créé. Donc, pour résoudre ce problème, nous pouvons copier la valeur dei
dans une variable dans le blocwhile {...}
, comme ceci :function makeArmy() { let shooters = []; let i = 0; while (i < 10) { let j = i; let shooter = function() { // fonction shooter alert( j ); // devrait afficher son numéro }; shooters.push(shooter); i++; } return shooters; } let army = makeArmy(); // Maintenant, le code fonctionne correctement army[0](); // 0 army[5](); // 5
Ici,
let j = i
déclare une variable “itération-locale”j
et y copiei
. Les primitives sont copiées “par valeur”, donc nous obtenons en fait une copie indépendante dei
, appartenant à l’itération de boucle courante.Les shooters fonctionnent correctement, car la valeur de
i
vit maintenant un peu plus près. Pas dans l’environnement lexicalmakeArmy()
, mais dans l’environnement lexical qui correspond à l’itération de la boucle actuelle :Ce genre de problème pourrait également être évité si nous utilisions
for
au début, comme ceci :function makeArmy() { let shooters = []; for(let i = 0; i < 10; i++) { let shooter = function() { // fonction shooter alert( i ); // devrait afficher son numéro }; shooters.push(shooter); } return shooters; } let army = makeArmy(); army[0](); // 0 army[5](); // 5
C’est essentiellement la même chose, car
for
génère un nouvel environnement lexical à chaque itération avec sa propre variablei
. Ainsi, leshooter
généré à chaque itération fait référence à son proprei
, à partir de cette itération même.Maintenant que vous avez déployé tant d’efforts pour lire ceci, et que la recette finale est si simple – utilisez simplement
for
, vous vous demandez peut-être – cela en valait-il la peine ?Eh bien, si vous pouviez facilement répondre à la question, vous ne liriez pas la solution. Donc, j’espère que cette tâche doit vous avoir aidé à comprendre un peu mieux les choses.
En outre, il existe en effet des cas où l’on préfère while
à for
, et d’autres scénarios où de tels problèmes sont réels.