var RelevantDiv = document.getElementById("RelevantDiv");
 for (var i = 0; i < 5; i++) {
    for (var j = 0; j < 5; j++) {
       var NewButton = document.createElement("NewButton");
       var IdString = "ID" + i + "_"  +j;
       NewButton.onclick = function() { DoStuff(IdString) };
       RelevantDiv.appendChild(NewButton);
    }
 } 

 function DoStuff(IdForStuff) {
    // Does stuff with id 
 }

Le problème est que chaque fois que NewButton.onclick est défini, il le définit sur le IdString final qui est "ID4_4".

0
IntegrateThis 9 août 2016 à 18:27

3 réponses

Meilleure réponse

Votre problème sous-jacent est que la variable utilisée dans le onclick rappel, IdString, a sa déclaration hissé en haut de la portée actuelle, c'est-à-dire la fonction dans laquelle il s'exécute. Cela signifie que chaque fois que vous êtes dans la boucle, sa valeur est remplacée - elle est fonctionnellement la même que celle-ci:

var IdString;

var RelevantDiv = document.getElementById("RelevantDiv");
for (var i = 0; i < 5; i++) {
   for (var j = 0; j < 5; j++) {
      var NewButton = document.createElement("NewButton");
      IdString = "ID" + i + "_"  +j;
      NewButton.onclick = function() { DoStuff(IdString) };
      RelevantDiv.appendChild(NewButton);
   }
} 

Vous devez vous assurer que vous capturez la bonne valeur de IdString lorsque vous en avez besoin, ce qui se fait généralement par l'utilisation d'une fermeture:

var RelevantDiv = document.getElementById("RelevantDiv");
for (var i = 0; i < 5; i++) {
   for (var j = 0; j < 5; j++) {
      (function(IdString) {
          var NewButton = document.createElement("NewButton");
          NewButton.onclick = function() { DoStuff(IdString) };
          RelevantDiv.appendChild(NewButton);
      })("ID" + i + "_"  +j)
   }
} 

Ici, nous créons une autre fonction interne pour créer une nouvelle portée pour contenir chaque valeur IdString individuelle. Il est ensuite immédiatement appelé avec la bonne valeur pour chaque itération des boucles. IdString est alors capturé dans cette fermeture, et la valeur correcte sera utilisée dans le onclick rappel.


Alternativement, vous pouvez lier l'argument à la dès que vous savez quelle est la valeur pertinente:

var RelevantDiv = document.getElementById("RelevantDiv");
for (var i = 0; i < 5; i++) {
   for (var j = 0; j < 5; j++) {
      var NewButton = document.createElement("NewButton");
      NewButton.onclick = DoStuff.bind(null, "ID" + i + "_"  +j);
      RelevantDiv.appendChild(NewButton);
   }
} 

Cela supprime les deux fonctions internes et affecte directement l'événement onclick à une copie de la fonction DoStuff qui aura le bon argument.

1
James Thorpe 9 août 2016 à 15:53

Le problème ici est que, lorsque le gestionnaire que vous affectez s'exécute, la boucle a déjà bouclé toutes les itérations et la variable à ce stade sera à son état final. Pour éviter cela, vous devez utiliser un verrou. Il implique une fonction auto-exécutable (function () { // code })() qui enregistre l'état actuel de la variable. Lorsque la variable dans l'itération en cours, par exemple State 2 est donnée comme argument à la fonction auto-exécutable, la portée de la fonction enregistrera cet état. Lorsque la boucle for continue, la variable d'origine changera toujours, mais la portée de la nouvelle "fonction interne" gardera la valeur "verrouillée". Ainsi, même lorsque la variable de boucle sera finalement à son 4ème état, la variable verrouillée sera toujours à l'état 2.

for (var i = 0; i < 5; i++) {
    button.addEventListener ("click", function () {
        // Here, i would normally always be 5
        (function (lockedIndex) {
            // This lock will make i the way it should be
            // You have to use lockedIndex instead of i
        })(i)
    });
}

Pour votre code, cela ressemblerait à quelque chose comme:

 var RelevantDiv = document.getElementById("RelevantDiv");
 for (var i = 0; i < 5; i++) {
    for (var j = 0; j < 5; j++) {
        (function (ix, jx) {
          var NewButton = document.createElement("NewButton");
          var IdString = "ID" + ix + "_"  +jx;
          NewButton.onclick = function() { DoStuff(IdString) };
          RelevantDiv.appendChild(NewButton);
        })(i, j)
    }
 } 

 function DoStuff(IdForStuff) {
    // Does stuff with id 
 }
2
NikxDa 9 août 2016 à 15:36

Il convient de noter que vous n'aurez pas de tels problèmes si vous utilisez let ou const au lieu de var:

for (let i = 0; i < 5; i++) {
    for (let j = 0; j < 5; j++) {
       const NewButton = document.createElement("NewButton");
       const IdString = "ID" + i + "_"  +j;
       NewButton.onclick = function() { DoStuff(IdString) };
       RelevantDiv.appendChild(NewButton);
    }
}
2
Adrian Wydmanski 9 août 2016 à 15:38