Un peu tard pour la fête, mais j'explorais ce problème aujourd'hui et j'ai remarqué que la plupart des réponses ne traitent pas complètement de la façon dont Javascript traite les portées, ce qui est essentiellement ce que cela se résume.
Ainsi, comme beaucoup d'autres l'ont mentionné, le problème est que la fonction interne fait référence à la même i
variable. Alors pourquoi ne créons-nous pas simplement une nouvelle variable locale à chaque itération, et avons-nous la référence de fonction interne à la place?
//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};
var funcs = {};
for (var i = 0; i < 3; i++) {
var ilocal = i; //create a new local variable
funcs[i] = function() {
console.log("My value: " + ilocal); //each should reference its own local variable
};
}
for (var j = 0; j < 3; j++) {
funcs[j]();
}
Tout comme auparavant, où chaque fonction interne a sorti la dernière valeur assignée à i
, maintenant chaque fonction interne sort juste la dernière valeur assignée à ilocal
. Mais chaque itération ne devrait-elle pas avoir la sienne ilocal
?
Il s'avère que c'est le problème. Chaque itération partage la même étendue, donc chaque itération après la première n'est que l'écrasement ilocal
. De MDN :
Important: JavaScript n'a pas de portée de bloc. Les variables introduites avec un bloc sont étendues à la fonction ou au script conteneur, et les effets de leur définition persistent au-delà du bloc lui-même. En d'autres termes, les instructions de bloc n'introduisent pas de portée. Bien que les blocs "autonomes" soient une syntaxe valide, vous ne voulez pas utiliser de blocs autonomes en JavaScript, car ils ne font pas ce que vous pensez qu'ils font, si vous pensez qu'ils font quelque chose comme de tels blocs en C ou Java.
Réitéré pour souligner:
JavaScript n'a pas de portée de bloc. Les variables introduites avec un bloc sont étendues à la fonction ou au script conteneur
Nous pouvons le voir en vérifiant ilocal
avant de le déclarer à chaque itération:
//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};
var funcs = {};
for (var i = 0; i < 3; i++) {
console.log(ilocal);
var ilocal = i;
}
C'est exactement pourquoi ce bug est si délicat. Même si vous redéclarez une variable, Javascript ne lancera pas d'erreur et JSLint ne lancera même pas d'avertissement. C'est aussi pourquoi la meilleure façon de résoudre ce problème est de tirer parti des fermetures, ce qui est essentiellement l'idée qu'en Javascript, les fonctions internes ont accès aux variables externes car les portées internes "renferment" les portées externes.
Cela signifie également que les fonctions internes "conservent" les variables externes et les maintiennent en vie, même si la fonction externe revient. Pour utiliser cela, nous créons et appelons une fonction wrapper uniquement pour créer une nouvelle portée, déclarons ilocal
dans la nouvelle portée et retournons une fonction interne qui utilise ilocal
(plus d'explications ci-dessous):
//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};
var funcs = {};
for (var i = 0; i < 3; i++) {
funcs[i] = (function() { //create a new scope using a wrapper function
var ilocal = i; //capture i into a local var
return function() { //return the inner function
console.log("My value: " + ilocal);
};
})(); //remember to run the wrapper function
}
for (var j = 0; j < 3; j++) {
funcs[j]();
}
La création de la fonction interne à l'intérieur d'une fonction wrapper donne à la fonction interne un environnement privé auquel elle seule peut accéder, une "fermeture". Ainsi, chaque fois que nous appelons la fonction wrapper, nous créons une nouvelle fonction interne avec son propre environnement séparé, garantissant que les ilocal
variables ne se heurtent pas et ne se remplacent pas. Quelques optimisations mineures donnent la réponse finale que de nombreux autres utilisateurs de SO ont donnée:
//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};
var funcs = {};
for (var i = 0; i < 3; i++) {
funcs[i] = wrapper(i);
}
for (var j = 0; j < 3; j++) {
funcs[j]();
}
//creates a separate environment for the inner function
function wrapper(ilocal) {
return function() { //return the inner function
console.log("My value: " + ilocal);
};
}
Mise à jour
Avec ES6 désormais intégré, nous pouvons désormais utiliser le nouveau let
mot clé pour créer des variables de portée bloc:
//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};
var funcs = {};
for (let i = 0; i < 3; i++) { // use "let" to declare "i"
funcs[i] = function() {
console.log("My value: " + i); //each should reference its own local variable
};
}
for (var j = 0; j < 3; j++) { // we can use "var" here without issue
funcs[j]();
}
Regardez comme c'est facile maintenant! Pour plus d'informations, consultez cette réponse , sur laquelle mes informations sont basées.
funcs
être un tableau, si vous utilisez des indices numériques? Juste un avertissement.