web-dev-qa-db-fra.com

Problème de boucle infâme javascript?

J'ai l'extrait de code suivant.

function addLinks () {
    for (var i=0, link; i<5; i++) {
        link = document.createElement("a");
        link.innerHTML = "Link " + i;
        link.onclick = function () {
            alert(i);
        };
        document.body.appendChild(link);
    }
}

Le code ci-dessus permet de générer 5 liens et de lier chaque lien à un événement d'alerte pour afficher l'id du lien actuel. Mais ça ne marche pas. Lorsque vous cliquez sur les liens générés, ils disent tous "lien 5".

Mais l'extrait de code suivant fonctionne comme notre attente.

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);
    }
}

Les 2 extraits ci-dessus sont cités de ici . Comme l'explique l'auteur, il semble que la fermeture fasse la magie.

Mais comment cela fonctionne et comment la fermeture le fait fonctionner sont au-delà de ma compréhension. Pourquoi le premier ne fonctionne pas pendant que le second fonctionne? Quelqu'un peut-il donner une explication détaillée de la magie?

merci.

216
Zhu Tao

me citer pour une explication du premier exemple:

Les portées de JavaScript sont au niveau de la fonction et non du bloc. La création d'une fermeture signifie simplement que la portée englobante est ajoutée à l'environnement lexical de la fonction incluse.

Une fois la boucle terminée, la variable au niveau de la fonction i a la valeur 5, et c’est ce que la fonction interne "voit".

Dans le deuxième exemple, pour chaque étape d'itération, le littéral de fonction externe sera évalué en tant que nouvel objet de fonction avec sa propre portée et variable locale num, dont la valeur est définie sur la valeur actuelle de i. Comme num n'est jamais modifié, il restera constant pendant toute la durée de la fermeture: la prochaine étape de l'itération ne remplacera pas l'ancienne valeur car les objets de la fonction sont indépendants.

Gardez à l'esprit que cette approche est plutôt inefficace car deux nouveaux objets fonction doivent être créés pour chaque lien. Ceci est inutile, car ils peuvent facilement être partagés si vous utilisez le nœud DOM pour le stockage d'informations:

function linkListener() {
    alert(this.i);
}

function addLinks () {
    for(var i = 0; i < 5; ++i) {
        var link = document.createElement('a');
        link.appendChild(document.createTextNode('Link ' + i));
        link.i = i;
        link.onclick = linkListener;
        document.body.appendChild(link);
    }
}
105
Christoph

J'aime écrire des explications simples pour les personnes épaisses, parce que je suis épaisse alors allez-y ...

Nous avons 5 divs sur la page, chacun avec un identifiant ... div1, div2, div3, div4, div5

jQuery peut le faire ...

for (var i=1; i<=5; i++) {
    $("#div" + i).click ( function() { alert ($(this).index()) } )
}

Mais vraiment résoudre le problème (et le construire lentement) ...

ÉTAPE 1

for (var i=1; i<=5; i++) {
    $("#div" + i).click (
        // TODO: Write function to handle click event
    )
}

ÉTAPE 2

for (var i=1; i<=5; i++) {
    $("#div" + i).click (
        function(num) {
            // A functions variable values are set WHEN THE FUNCTION IS CALLED!
            // PLEASE UNDERSTAND THIS AND YOU ARE HOME AND DRY (took me 2 years)!
            // Now the click event is expecting a function as a handler so return it
            return function() { alert (num) }
        }(i) // We call the function here, passing in i
    )
}

SIMPLE POUR COMPRENDRE L'ALTERNATIVE

Si vous ne comprenez pas cela, alors cela devrait être plus facile à comprendre et aura le même effet ...

for (var i=1; i<=5; i++) {

    function clickHandler(num) {    
        $("#div" + i).click (
            function() { alert (num) }
        )
    }
    clickHandler(i);

}

Cela devrait être simple à comprendre si vous vous rappelez que les valeurs d'une variable de fonction sont définies lorsque la fonction est appelée (mais cela utilise exactement le même processus de pensée qu'auparavant)

78
Daniel Lewis

En gros, dans le premier exemple, vous liez le i à l'intérieur du gestionnaire onclick directement au i à l'extérieur du gestionnaire onclick. Ainsi, lorsque le i en dehors du gestionnaire onclick change, le i du gestionnaire onclick change également.

Dans le deuxième exemple, au lieu de le lier au num du gestionnaire onclick, vous le transmettez à une fonction qui le lie ensuite au num du onclick gestionnaire. Lorsque vous le transmettez à la fonction, la valeur de i est copiée et non liée à num. Ainsi, lorsque i change, num reste le même. La copie se produit parce que les fonctions en JavaScript sont des "fermetures", ce qui signifie qu'une fois que quelque chose est passé dans la fonction, il est "fermé" pour une modification extérieure.

20
Imagist

D'autres ont expliqué ce qui se passe, voici une solution alternative.

function addLinks () {
  for (var i = 0, link; i < 5; i++) {
    link = document.createElement("a");
    link.innerHTML = "Link " + i;

    with ({ n: i }) {
      link.onclick = function() {
        alert(n);
      };
    }
    document.body.appendChild(link);
  }
}

Fondamentalement, le pauvre homme laissait ses liens.

17
nlogax

Dans le premier exemple, vous liez simplement cette fonction à l'événement onclick:

function() {alert(i);};

Cela signifie que sur l'événement click, js doit alerter la valeur de la variable addlink des fonctions addlink. Sa valeur sera 5 à cause de la boucle for ().

Dans le deuxième exemple, vous générez une fonction à associer à une autre fonction:

function (num) {
  return function () { alert(num); };
}

Cela signifie: si appelé avec une valeur, retournez-moi une fonction qui alertera la valeur entrée. Par exemple. appeler function(3) retournera function() { alert(3) };.

Vous appelez cette fonction avec la valeur i à chaque itération. Vous créez donc des fonctions onclick distinctes pour chaque lien.

Le fait est que dans le premier exemple, votre fonction contenait une référence de variable, tandis que dans le second, à l'aide de la fonction externe, vous avez substitué la référence à une valeur réelle. Cela s'appelle une fermeture à peu près parce que vous "entourez" la valeur actuelle d'une variable dans votre fonction au lieu de garder une référence à celle-ci.

5
Zed