115 votes

AddEventListener utilisant une boucle for et passant des valeurs

Je suis en train d'essayer d'ajouter un écouteur d'événements à plusieurs objets en utilisant une boucle for, mais je me retrouve avec tous les écouteurs ciblant 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 la boucle for-loop addEvent qui ne fonctionne pas comme je l'espérais. Peut-être que j'utilise la mauvaise approche dans l'ensemble.

Exemple en utilisant 4 de la classe "container" Le déclenchement sur le conteneur 4 fonctionne comme prévu. Le déclenchement sur les contenurs 1,2,3 déclenche l'événement sur le conteneur 4, mais seulement si le déclencheur a déjà été activé.

// Fonction à exécuter au clic :
function makeItHappen(elem, elem2) {
  var el = document.getElementById(elem);
  el.style.backgroundColor = "red";
  var el2 = document.getElementById(elem2);
  el2.style.backgroundColor = "blue";
}

// Fonction d'auto-chargement pour ajouter les écouteurs :
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);
}

    some text

    some text

    some text

    some text

163voto

Halcyon Points 31203

Fermetures! :D

Ce code corrigé fonctionne comme vous l'avez voulu:

// Fonction à exécuter au clic :
function makeItHappen(elem, elem2) {
    var el = document.getElementById(elem);
    el.style.backgroundColor = "red";
    var el2 = document.getElementById(elem2);
    el2.style.backgroundColor = "blue";
}

// Fonction d'auto-chargement pour ajouter les écouteurs :
var elem = document.getElementsByClassName("triggerClass");

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);
    }()); // invocation immédiate
}

    some text

    some text

    some text

    some text

Pourquoi cela corrige-t-il le problème?

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);
}

Il s'agit en réalité de JavaScript non strict. Il est interprété ainsi:

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 la remontée des variables, les déclarations var sont déplacées en haut de la portée. Comme JavaScript n'a pas de portée de bloc (for, if, while etc.), elles sont déplacées en haut de la fonction. Mise à jour : à partir d'ES6, vous pouvez utiliser let pour obtenir des variables à portée de bloc.

Lorsque votre code s'exécute, ce qui se passe est le suivant : dans la boucle for, vous ajoutez les rappels de clic et vous assignez boxa, mais sa valeur est écrasée dans l'itération suivante. Lorsque l'événement de clic se déclenche, le rappel s'exécute et la valeur de boxa est toujours le dernier élément de la liste.

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


Des outils d'analyse de code tels que JSLint ou JSHint pourront détecter du code suspect comme celui-ci. Si vous écrivez beaucoup de code, il est utile de prendre le temps d'apprendre à utiliser ces outils. Certains IDE les intègrent.

17voto

msantos Points 471

Vous pouvez utiliser la liaison de fonction. Vous n'avez pas besoin d'utiliser de 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);
   }
}

15voto

tenbits Points 1269

Vous êtes confronté au problème de portée/fermeture en tant que function(){makeItHappen(boxa,boxb);} boxa et boxb font toujours 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);

2voto

olindk Points 91

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 d'exemple et votre code sont essentiellement les mêmes, c'est une erreur courante pour ceux qui ne connaissent pas les "fermetures".

Pour simplifier, lorsque vous créez une fonction gestionnaire à l'intérieur de addEvents(), elle n'accède pas seulement à la variable i de l'environnement de addEvents(), mais elle "se souvient" aussi de i.

Et parce que votre gestionnaire "se souvient" de i, la variable i ne disparaîtra pas une fois que addEvents() a été exécuté.

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

1voto

blue3d Points 11

J'ai également eu ce problème il y a quelque temps. Je l'ai résolu en utilisant une fonction "ajouts" en dehors de la boucle, pour ajouter des événements, et cela a parfaitement fonctionné.

Votre script devrait ressembler à ceci.

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 ---------------------------|
    ajouts(boxa, boxb);
  }
}
//- fonction ajouts ----|
function ajouts(obj1, obj2){
  obj1.addEventListener("click", function(){makeItHappen(obj1, obj2);}, false);
  obj2.addEventListener("click", function(){makeItHappen(obj1, obj2);}, false);
}
//- fin édition -----------------------|

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X