web-dev-qa-db-fra.com

Veuillez expliquer l'utilisation des fermetures JavaScript dans les boucles

J'ai lu un certain nombre d'explications sur les fermetures et les fermetures à l'intérieur des boucles. J'ai du mal à comprendre le concept. J'ai ce code: Existe-t-il un moyen de réduire le code autant que possible afin que le concept de fermeture puisse être clarifié. J'ai du mal à comprendre la partie dans laquelle le i est entre deux parenthèses. Merci

function addLinks () {
    for (var i=0, link; i<5; i++) {

        link = document.createElement("a");
        link.innerHTML = "Link " + i;


        link.onclick = function (num) {
            return function () {
                alert(num);
            };
        }(i);
        document.body.appendChild(link);

    }
}
window.onload = addLinks;
54
CMS scripting

AVERTISSEMENT: réponse longue (ish)

Ceci est copié directement à partir d'un article que j'ai écrit dans un wiki interne à l'entreprise:

Question: Comment utiliser correctement les fermetures dans les boucles? Réponse rapide: utilisez une usine de fonctions.

  for (var i=0; i<10; i++) {
    document.getElementById(i).onclick = (function(x){
      return function(){
        alert(x);
      }
    })(i);
  }

ou la version plus lisible:

  function generateMyHandler (x) {
    return function(){
      alert(x);
    }
  }

  for (var i=0; i<10; i++) {
    document.getElementById(i).onclick = generateMyHandler(i);
  }

Cela confond souvent les gens qui sont nouveaux dans la programmation javascript ou fonctionnelle. C'est le résultat d'un malentendu sur les fermetures.

Une fermeture ne transmet pas simplement la valeur d'une variable ou même une référence à la variable. Une fermeture capture la variable elle-même! Le bit de code suivant illustre cela:

  var message = 'Hello!';
  document.getElementById('foo').onclick = function(){alert(message)};
  message = 'Goodbye!';

En cliquant sur l'élément 'foo', une boîte d'alerte apparaîtra avec le message: "Au revoir!". Pour cette raison, l'utilisation d'une fermeture simple dans une boucle se terminera avec toutes les fermetures partageant la même variable et cette variable contiendra la dernière valeur qui lui est affectée dans la boucle. Par exemple:

  for (var i=0; i<10; i++) {
    document.getElementById('something'+i).onclick = function(){alert(i)};
  }

Tous les éléments cliqués généreront une boîte d'alerte avec le nombre 10. En fait, si nous faisons maintenant i="hello"; tous les éléments vont maintenant générer une alerte "bonjour"! La variable i est partagée entre dix fonctions PLUS la fonction/portée/contexte actuel. Considérez-le comme une sorte de variable globale privée que seules les fonctions impliquées peuvent voir.

Ce que nous voulons, c'est une instance de cette variable ou au moins une simple référence à la variable au lieu de la variable elle-même. Heureusement, javascript a déjà un mécanisme pour passer une référence (pour les objets) ou une valeur (pour les chaînes et les nombres): arguments de fonction!

Lorsqu'une fonction est appelée en javascript, les arguments de cette fonction sont passés par référence s'il s'agit d'un objet ou par valeur s'il s'agit d'une chaîne ou d'un nombre. Cela suffit pour rompre le partage variable dans les fermetures.

Donc:

  for (var i=0; i<10; i++) {
    document.getElementById(i).onclick =
      (function(x){ /* we use this function expression simply as a factory
                       to return the function we really want to use: */

        /* we want to return a function reference
           so we write a function expression*/
        return function(){
          alert(x); /* x here refers to the argument of the factory function
                       captured by the 'inner' closure */
        }

      /* The brace operators (..) evaluates an expression, in this case this
         function expression which yields a function reference. */

      })(i) /* The function reference generated is then immediately called()
               where the variable i is passed */
  }
110
slebetman

Je programme en JavaScript depuis longtemps et la "fermeture en boucle" est un sujet très large. Je suppose que vous parlez de la pratique d'utiliser (function(param) { return function(){ ... }; })(param); à l'intérieur d'une boucle for afin de conserver la "valeur actuelle" de la boucle lors de l'exécution ultérieure de cette fonction interne ...

Le code:

for(var i=0; i<4; i++) {
  setTimeout(
    // argument #1 to setTimeout is a function.
    // this "outer function" is immediately executed, with `i` as its parameter
    (function(x) {
      // the "outer function" returns an "inner function" which now has x=i at the
      // time the "outer function" was called
      return function() {  
        console.log("i=="+i+", x=="+x);
      };
    })(i) // execute the "closure" immediately, x=i, returns a "callback" function
  // finishing up arguments to setTimeout
  , i*100);
}

Production:

i==4, x==0
i==4, x==1
i==4, x==2
i==4, x==3

Comme vous pouvez le voir par la sortie, toutes les fonctions de rappel internes pointent toutes vers le même i, cependant, puisque chacune a sa propre "fermeture", la valeur de x est en fait stockée sous la forme quel que soit i était au moment de l'exécution de la fonction externe.

Généralement, lorsque vous voyez ce modèle, vous utilisez le même nom de variable que le paramètre et l'argument de la fonction externe: (function(i){ })(i) par exemple. Tout code à l'intérieur de cette fonction (même s'il est exécuté plus tard, comme une fonction de rappel) fera référence à i au moment où vous avez appelé la "fonction externe".

10
gnarf

Eh bien, le "problème" avec les fermetures dans un tel cas est que tout accès à i référencerait la même variable. C'est à cause de ECMA-/Javascriptsfunction scope Ou lexical scope.

Donc, pour éviter que chaque appel à alert(i); affiche un 5 (Car une fois la boucle terminée i === 5), vous devez créer une nouvelle fonction qui s'appelle elle-même au moment de l'exécution.

Pour ce faire, vous devez créer une nouvelle fonction, plus vous avez besoin de la parenthèse supplémentaire à la fin, pour invoke the outer function Immédiatement, donc link.onclick A maintenant la fonction retournée comme référence.

4
jAndy

Une fermeture est une construction dans laquelle vous référencez une variable en dehors de la portée dans laquelle elle est définie. Vous parlez généralement de fermetures dans le contexte d'une fonction.

var helloFunction;
var finished = false;

while (!finished) {
 var message = 'Hello, World!';
 helloFunction = function() {
   alert(message);
 }
 finished = true;
}

helloFunction();

Ici, je définis le message variable , et je définis une fonction qui référence le message . Quand je définis la fonction à utiliser message, je crée une fermeture . Cela signifie que helloFunction contient une référence au message , afin que je puisse continuer à utiliser message , même en dehors de la portée (le corps de la boucle) où message est défini.

Addendum

Le (i) entre parenthèses est un appel de fonction. Ce qui se passe c'est:

  1. Vous définissez une fonction (num) {}. Cela s'appelle une fonction anonyme , car elle est définie en ligne et n'a pas de nom.
  2. function (num) prend un argument entier et retourne une référence à une autre fonction, qui est définie comme alert (num)
  3. La fonction anonyme externe est immédiatement appelée, avec l'argument i . Donc num = i . Le résultat de cet appel est une fonction qui fera l'alerte (i).
  4. Le résultat final est plus ou moins équivalent à: link.onclick = function() { alert(i); };
2
RMorrisey

Pour répondre à la dernière partie de vos questions. Les deux parenthèses invoquent la fonction comme toutes les autres fonctions. Pourquoi vous le faites ici, c'est que vous voulez garder ce que la variable "i" est juste à ce moment-là. Donc, ce qu'il fait, appelez la fonction, le i est envoyé comme argument "num". Puisqu'il est invoqué, il se souviendra de la valeur nume dans le scoop des liens variables.

Si vous ne le faites pas sur ce lien, un clic entraînerait une alerte disant "5"

John Resig, fondateur de jQuery, a une très belle présentation en ligne expliquant cela. http://ejohn.org/apps/learn/

..fredrik

0
fredrik