Comment attribuer à des objets un onclick faisant appel à une variable de boucle ?

Pour garder la structure actuelle de la question, je pense exposer le problème rencontré quand on utilise var — ce qui n’est pas inenvisageable aujourd’hui, compte tenu de la prolifération de docs JS obsolètes sur le Web — puis proposer une solution où l’on remplace simplement var par let, en expliquant très rapidement le fonctionnement de let.

Je propose de rajouter un 3e paragraphe à l’attention de celles et ceux qui développent sur des vieilles plateformes où ES2015+ n’est pas disponible. Les débutantes et débutants ne savent pas forcément que même un framework ne peut pas « tasser » cette incompatibilité.

Dans tous les cas, la solution actuelle me paraît obsolète, car elle utilise les propriétés expando, technique généralement déconseillée aujourd’hui. Je propose une solution utilisant une fonction anonyme (IIFE).

Un peu hors sujet, je me suis interrogé sur le genre de div (« un div » ou « une div » ?). Je penche plutôt pour le féminin mais, comme c’est de l’ordre du détail, j’ai respecté la version originale.

Dans des cas comme celui ci-dessous, où l’on souhaite créer des éléments et leur attribuer un événement onclick dépendant d’une variable de boucle :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
function foo() {
  for (var i = 0; i < 10; i++) {
    var MonDiv = document.createElement("div");
    MonDiv.textContent = "div " + i;
    MonDiv.onclick = function () { alert(i); };
    document.body.appendChild(MonDiv);
  }
}
Cette fonction est censée permettre de créer 10 nouveaux divs contenant les textes « div 1 », « div 2 », etc. et de les insérer dans la page. On leur attribue également un onclick avec pour but de faire un alert() de « 1 » sur le premier div, « 2 » sur le second, etc.

Malheureusement cela ne fonctionne pas : les divs sont correctement créés et insérés mais au clic, tous affichent le message « 10 ». En fait, la valeur de i n’est pas évaluée au moment de la mise en place du onclick, mais seulement au moment où celui-ci est appelé. Or à ce moment, sa valeur vaut bien 10, la dernière valeur de la boucle.

La solution moderne (fin 2017) est d’utiliser le mot-clé let, apparu avec ECMAScript 2015, qui fonctionne un peu comme var mais qui limite la portée des variables au bloc courant, en l’occurence le corps de la boucle for.

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
function foo() {
  for (let i = 0; i < 10; i++) {
    let MonDiv = document.createElement("div");
    MonDiv.textContent = "div " + i;
    MonDiv.onclick = function () { alert(i); };
    document.body.appendChild(MonDiv);
  }
}
Si vous développez dans un environnement où let n’est pas disponible, il faut recourir à une fonction anonyme :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
function foo() {
  for (var i = 0; i < 10; i++) {
    var MonDiv = document.createElement("div");
    MonDiv.textContent = "div " + i;
 
    (function (arg) {
      MonDiv.onclick = function () { alert(arg); };
    }(i));
 
    document.body.appendChild(MonDiv);
  }
}
Cela fonctionne car la fonction anonyme copie la valeur de i dans son argument arg au moment où elle est appelée, c’est-à-dire au moment où la boucle for est exécutée.
Voilà, j’ai essayé de faire aussi clair et concis que possible, mais si vous trouvez qu’un passage mérite d’être reformulé, n’hésitez pas