Quizz JavaScript : réponse gestionnaire d'evenements et boucle
Voici la réponse au Quizz JavaScript - gestionnaire d'evenements et boucles.
Pour rappel la question était pourquoi lorsque l'on click sur un bouton créé avec le code ci-dessous, il nous affiche toujours 'button N°10' ?
var div = $get('div1');
for (var i = 0; i < 10; i++){
var btn = document.createElement('button');
btn.appendChild(document.createTextNode('button N°' + i));
btn.onclick = function(){
alert('button N°' + i);
}
div.appendChild(btn);
div.appendChild(document.createElement('br'));
}
C'est en fait un problème de scope, il faut savoir que la portée des variables en js est global à la fonction, c'est à dire que le i se trouvant dans le onclick est le même que celui de la boucle for. C'est au moment où l'on click sur un bouton que la fonction est interprété, i se rapportera toujours au i de la boucle for, c'est pour cela que i vaut 10 alors que le dernier boutton a pour texte "button N°9". Pour contourner le problème il faut créer un nouveau contexte de scope c'est à dire éxecuter une nouvelle function.
btn.onclick = function(j){
return function(){
alert(j);
}
}(i);
Ce code exécute une fonction qui retourne une fonction, comme il y a exécution de fonction, nous avons un nouveau contexte et donc une nouveau scope.
Microsoft Ajax Library possède une fonction qui fait exactement la même chose qu'au dessus : Function.createCallback (non documenté ...)
btn.onclick = Function.createCallback(function(j){
Sys.Debug.trace(j);
}, i);
Il existe une autre astuce qui consiste à utiliser un expando attribute. JavaScript étant un langage dynamique on peut modifier le type d'un objet à la volée et donc rajouter une propriété comme on veut. C'est la solution la plus courante car la plus simple à comprendre.
btn.__i = i;
btn.onclick = function(){
alert(this.__i);
}
Il existe enfin une dernière solution que je déconseille, il s'agit de créer une function en utilisant le constructeur de l'objet Function ou à l'aide de la méthode eval (le constructeur de Function utilise eval en interne).
btn.onclick = new Function('alert(' + i + ')');
Je déconseille cette solution pour deux raisons : d'une part niveau perf, eval est une fonction très couteuse. D'autre part puisque l'on définit la fonction à partir d'un string il faut que l'objet que l'on veut passer (i dans notre exemple) soit représentable sous la forme d'un string. Ce qui n'est pas forcément le cas pour des objets complexes.
Pour en savoir plus : Scope in JavaScript et Understanding scope and binding in JavaScript
Félicitation à Simon Ferquel, gldfdp, FremyCompany d'avoir trouvé les bonnes réponses.