web-dev-qa-db-fra.com

Comment passer la valeur (pas la référence) d'une variable JS à une fonction?

Voici une version simplifiée de quelque chose que j'essaie d'exécuter:

for (var i = 0; i < results.length; i++) {
    marker = results[i];
    google.maps.event.addListener(marker, 'click', function() { 
        change_selection(i);
    }); 
}

mais je constate que chaque écouteur utilise la valeur de results.length (la valeur à la fin de la boucle for). Comment puis-je ajouter des écouteurs de telle sorte que chacun utilise la valeur de i au moment où je l'ajoute, plutôt que la référence à i?

115
ryan

Dans les navigateurs modernes, vous pouvez utiliser les mots clés let ou const pour créer une variable de portée bloc:

for (let i = 0; i < results.length; i++) {
  let marker = results[i];
  google.maps.event.addListener(marker, 'click', () => change_selection(i));
}

Dans les navigateurs plus anciens, vous devez créer une portée distincte qui enregistre la variable dans son état actuel en la passant comme paramètre de fonction:

for (var i = 0; i < results.length; i++) {
  (function (i) {
    marker = results[i];
    google.maps.event.addListener(marker, 'click', function() { 
      change_selection(i);
    }); 
  })(i);
}

En créant une fonction anonyme et en l'appelant avec la variable comme premier argument, vous passez par valeur à la fonction et créez une fermeture.

161
Andy E

Outre les fermetures, vous pouvez utiliser function.bind:

google.maps.event.addListener(marker, 'click', change_selection.bind(null, i));

transmet la valeur de i in comme argument à la fonction lorsqu'elle est appelée. (null est pour la liaison this, dont vous n'avez pas besoin dans ce cas.)

function.bind a été introduit par le framework Prototype et a été normalisé dans ECMAScript Fifth Edition. Jusqu'à ce que les navigateurs le prennent tous en charge de manière native, vous pouvez ajouter votre propre function.bind prise en charge à l'aide de fermetures:

if (!('bind' in Function.prototype)) {
    Function.prototype.bind= function(owner) {
        var that= this;
        var args= Array.prototype.slice.call(arguments, 1);
        return function() {
            return that.apply(owner,
                args.length===0? arguments : arguments.length===0? args :
                args.concat(Array.prototype.slice.call(arguments, 0))
            );
        };
    };
}
35
bobince

fermetures:

for (var i = 0, l= results.length; i < l; i++) {
    marker = results[i];
    (function(index){
        google.maps.event.addListener(marker, 'click', function() { 
            change_selection(index);
        }); 
    })(i);
}

EDIT, 2013: Ceux-ci sont maintenant communément appelés IIFE

13
David Murdoch

Vous vous retrouvez avec une fermeture. Voici un article sur les fermetures et comment travailler avec eux. Consultez l'exemple 5 sur la page; c'est le scénario auquel vous avez affaire.

EDIT: Quatre ans plus tard, ce lien est mort. La racine du problème ci-dessus est que la boucle for forme des fermetures (en particulier sur marker = results[i]). Lorsque marker est passé dans addEventListener, vous voyez l'effet secondaire de la fermeture: "l'environnement" partagé est mis à jour à chaque itération de la boucle, avant d'être finalement "enregistré" via la fermeture après l'itération finale. MDN l'explique très bien.

2
ajm