web-dev-qa-db-fra.com

addEventListener utilisant les valeurs de boucle et de passage

J'essaie d'ajouter l'écouteur d'événements à plusieurs objets à l'aide d'une boucle for, mais tous les écouteurs ciblent le même objet -> le dernier.

Si j'ajoute les écouteurs manuellement en définissant boxa et boxb pour chaque instance, cela fonctionne. Je suppose que c'est l'addEvent for-loop qui ne fonctionne pas comme je l'espérais. Peut-être que j'utilise la mauvaise approche complètement.

Exemple utilisant 4 de la classe = "conteneur" Le déclencheur sur le conteneur 4 fonctionne comme prévu. Déclencheur sur le conteneur 1, 2, 3 déclencheur sur le conteneur 4, mais uniquement si le déclencheur a déjà été activé.

// Function to run on click:
function makeItHappen(elem, elem2) {
  var el = document.getElementById(elem);
  el.style.backgroundColor = "red";
  var el2 = document.getElementById(elem2);
  el2.style.backgroundColor = "blue";
}

// Autoloading function to add the listeners:
var elem = document.getElementsByClassName("triggerClass");

for (var i = 0; i < elem.length; i += 2) {
  var k = i + 1;
  var boxa = elem[i].parentNode.id;
  var boxb = elem[k].parentNode.id;

  elem[i].addEventListener("click", function() {
    makeItHappen(boxa, boxb);
  }, false);
  elem[k].addEventListener("click", function() {
    makeItHappen(boxb, boxa);
  }, false);
}
<div class="container">
  <div class="one" id="box1">
    <p class="triggerClass">some text</p>
  </div>
  <div class="two" id="box2">
    <p class="triggerClass">some text</p>
  </div>
</div>

<div class="container">
  <div class="one" id="box3">
    <p class="triggerClass">some text</p>
  </div>
  <div class="two" id="box4">
    <p class="triggerClass">some text</p>
  </div>
</div>
73
user2919172

Fermetures! :RÉ

Ce code fixe fonctionne comme vous le souhaitez:

for (var i = 0; i < elem.length; i += 2) {
    (function () {
        var k = i + 1;
        var boxa = elem[i].parentNode.id;
        var boxb = elem[k].parentNode.id;

        elem[i].addEventListener("click", function() { makeItHappen(boxa,boxb); }, false);
        elem[k].addEventListener("click", function() { makeItHappen(boxb,boxa); }, false);
    }()); // immediate invocation
}

Pourquoi cela corrige-t-il?

for(var i=0; i < elem.length; i+=2){
    var k = i + 1;
    var boxa = elem[i].parentNode.id;
    var boxb = elem[k].parentNode.id;

    elem[i].addEventListener("click", function(){makeItHappen(boxa,boxb);}, false);
    elem[k].addEventListener("click", function(){makeItHappen(boxb,boxa);}, false);
}

Est-ce réellement JavaScript non-strict. C'est interprété comme ça:

var i, k, boxa, boxb;
for(i=0; i < elem.length; i+=2){
    k = i + 1;
    boxa = elem[i].parentNode.id;
    boxb = elem[k].parentNode.id;

    elem[i].addEventListener("click", function(){makeItHappen(boxa,boxb);}, false);
    elem[k].addEventListener("click", function(){makeItHappen(boxb,boxa);}, false);
}

En raison de variable levage , les déclarations var sont déplacées vers le haut de la portée. Puisque JavaScript n'a pas de portée de bloc (for, if, while, etc.), ils sont déplacés en haut de la fonction. Mise à jour: à partir de ES6, vous pouvez utiliser let pour obtenir des variables à périmètre réduit.

Lorsque votre code est exécuté, il se passe ce qui suit: dans la boucle for, vous ajoutez les rappels de clic et vous affectez boxa, mais sa valeur est remplacée à la prochaine itération. Lorsque l'événement click déclenche le rappel, la valeur de boxa est toujours le dernier élément de la liste.

En utilisant une fermeture (en fermant les valeurs de boxa, boxb etc.), vous liez la valeur à la portée du gestionnaire de clics.


Les outils d’analyse de code tels que JSLint ou JSHint seront en mesure d’attraper un code suspect comme celui-ci. Si vous écrivez beaucoup de code, il est intéressant de prendre le temps d'apprendre à utiliser ces outils. Certains IDE les ont intégrés.

113
Halcyon

Vous êtes confronté au problème de portée/fermeture comme function(){makeItHappen(boxa,boxb);}boxa et boxb fait ensuite référence au dernier élément.

Pour résoudre le problème:

function makeItHappenDelegate(a, b) {
  return function(){
      makeItHappen(a, b)
  }
}

// ...
 elem[i].addEventListener("click", makeItHappenDelegate(boxa,boxb), false);
12
tenbits

Vous pouvez utiliser la fonction Reliure.Vous n'avez pas besoin d'utiliser des fermetures.Voir ci-dessous:

Avant:

function addEvents(){
   var elem = document.getElementsByClassName("triggerClass");

   for(var i=0; i < elem.length; i+=2){
      var k = i + 1;
      var boxa = elem[i].parentNode.id;
      var boxb = elem[k].parentNode.id;

      elem[i].addEventListener("click", function(){makeItHappen(boxa,boxb);}, false);
      elem[k].addEventListener("click", function(){makeItHappen(boxb,boxa);}, false);
   }
}

Après:

function addEvents(){
   var elem = document.getElementsByClassName("triggerClass");

   for(var i=0; i < elem.length; i+=2){
        var k = i + 1;
      var boxa = elem[i].parentNode.id;
      var boxb = elem[k].parentNode.id;
      elem[i].addEventListener("click", makeItHappen.bind(this, boxa, boxb), false);
      elem[k].addEventListener("click", makeItHappen.bind(this, boxa, boxb), false);
   }
}
6
msantos

J'ai aussi eu ce problème il y a un moment. Je l'ai résolu en utilisant une fonction "ajoute" en dehors de la boucle, pour attribuer des événements, et cela a parfaitement fonctionné.

Votre script devrait ressembler.

function addEvents(){
  var elem = document.getElementsByClassName("triggerClass");
  for(var i=0; i < elem.length; i+=2){
    var k = i + 1;
    var boxa = elem[i].parentNode.id;
    var boxb = elem[k].parentNode.id;

//- edit ---------------------------|
    adds(boxa, boxb);
  }
}
//- adds function ----|
function adds(obj1, obj2){
  obj1.addEventListener("click", function(){makeItHappen(obj1, obj2);}, false);
  obj2.addEventListener("click", function(){makeItHappen(obj1, obj2);}, false);
}
//- end edit -----------------------|

function makeItHappen(elem, elem2){
  var el = document.getElementById(elem);
  el.style.transform = "flip it";
  var el2 = document.getElementById(elem2);
  el2.style.transform = "flip it";
}
1
blue3d

C'est à cause des fermetures.

Regardez ceci: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures#Creating_closures_in_loops_A_common_mistake

Le code exemple et votre code sont essentiellement les mêmes, c’est une erreur courante pour ceux qui ne savent pas "fermer".

En termes simples, lorsque vous créez une fonction de gestionnaire dans addEvents(), elle n’accède pas seulement à la variable i à partir de l’environnement de addEvents(), mais elle "se souvient" également. i.

Et parce que votre gestionnaire "se souvient" i, la variable i ne disparaîtra pas après l'exécution de addEvents().

Donc, quand le gestionnaire est appelé, il utilisera le i mais la variable i sera maintenant, après la boucle for, 3.

1
olindk