Un bug bien classique qui permet de parler de JS et du scope

J’ai pu débugger récemment ce bout de code:

1
2
3
4
5
6
// on a n boutons, cliquer sur le bouton "i" a pour effet de cacher un lien correspondant
for (var i=0; i<nbCheckboxes; i++) {
  $('input[name="val' + i + '"]').click(function() {
    $('#link'+i).hide();
  });
}

Etrangement, quelque soit le bouton cliqué, rien ne se passait… Après vérification, à chaque clic jQuery essayait de me cacher le lien « #link5 » (sachant que j’avais 5 liens, numérotés de 0 à 4, ça pouvait moyennement marcher).

Mine de rien, ce petit bug peut arriver. Rarement, parce qu’on a rarement un code aussi mal foutu hein :) mais ça peut arriver. Et ce petit bug de rien du tout relève d’une feature mal comprise de Javascript que j’appelle les « contextes dynamiques », mais qui relève simplement de la portée des variables.

Dans notre cas la variable « i » est déclarée au-dessus de la fonction anonyme qu’on affecte au clic, et le « i » référencé dans cette fonction est donc le même que celui déclaré dans la boucle « for ». Ainsi, quand il s’incrémente dans notre boucle, il s’est aussi incrémenté dans notre (ou plutôt nos, d’ailleurs) fonction(s) anonyme(s). Ainsi, à la fin de la boucle il vaudra nbCheckboxes+1, rendant complètement caduque toutes les fonctions qu’on a créées.

La solution est de créer un contexte d’exécution qui va embarquer sa propre version de « i » pour qu’il ne soit plus modifié par le contexte parent:

1
2
3
4
5
6
7
8
9
10
for (var x=0; x<nbCheckboxes; x++) {
  (function(i) { // nouveau contexte d'exécution dans une fonction anonyme, avec un "i" en
    // paramètre. Ce "i" n'est pas impacté par tout ce qui peut se passer au-dessus, puisqu'il
    // est interne à ce contexte.
    $('input[name="val' + i + '"]').click(function() {
      $('#link'+i).hide();
    });
  })(x); // on passe à notre fonction anonyme (qu'on pourrait justement ici appeler
  // "contexte dynamique") la valeur courante de x
}

Et cette fois ça marche. Si vous n’avez pas compris du premier coup ce qui s’est passé, c’est que vous avez des choses à apprendre qui pourraient bien vous éclairer sur la puissance cachée de ce langage 😉 donc relisez attentivement, et enjoy!

6 réflexions au sujet de « Un bug bien classique qui permet de parler de JS et du scope »

    1. naholyr Auteur de l’article

      Je n’y aurais pas pensé parce que je ne connaissais pas 😉 Par contre après avoir vérifié de quoi il s’agissait, je n’y penserai toujours pas \o/

      Ce n’est supporté que par Firefox & Safari: exit Opera & IE.
      Le code n’en est pas simplifié suffisamment pour en valoir la peine.

      Pour reprendre l’exemple donné par Mozilla:

      1
      2
      3
      4
      5
      
      var x = 5, y = 0;
      let (x = x+10, y = 12) {
        print(x+y + "\n");
      }
      print(x+y + "\n");

      Le code strictement équivalent sans let:

      1
      2
      3
      4
      5
      
      var x = 5, y = 0;
      (function(x, y) {
        print(x+y + "\n");
      })(x+10, 12);
      print(x+y + "\n");

      Pas assez décisif pour se couper d’un tiers du marché ^^

      Répondre
  1. Palleas

    Bon à savoir :)

    Au pire si tu ne veux pas t’embêter avec un contexte dynamique, tu peux passer par bind au lieu de click, qui permet de passer des informations à l’évènement qui sera diffusé (cf la doc). Je suppose que c’est finalement ce qu’il fait en interne, le coup du scope.

    Répondre
  2. Ivan Enderlin

    1/3 du marché, peut-être, mais tu parlais de Javascript, je réponds par du Javascript ;-). Après, il y a une réalité du marché qui fait que … mais la solution est dans le let. Tu le sauras pour dans 6 mois :-).

    Répondre

Laisser un commentaire