60 votes

N'est pas prise en charge de JavaScript fermetures avec des variables locales?

Je suis très perplexe à propos de ce code:

var closures = [];
function create() {
  for (var i = 0; i < 5; i++) {
    closures[i] = function() {
      alert("i = " + i);
    };
  }
}

function run() {
  for (var i = 0; i < 5; i++) {
    closures[i]();
  }
}

create();
run();

De ma compréhension, il doit imprimer 0,1,2,3,4 (n'est-ce pas le concept de fermetures?).

Au lieu de cela, il imprime 5,5,5,5,5.

J'ai essayé de Rhino et Firefox.

Quelqu'un pourrait expliquer ce comportement pour moi? Merci à l'avance.

61voto

Christoph Points 64389

Fixe de Jon répondre par l'ajout d'une fonction anonyme:

function create() {
  for (var i = 0; i < 5; i++) {
    closures[i] = (function(tmp) {
        return function() {
          alert("i = " + tmp);
        };
    })(i);
  }
}

L'explication est que du JavaScript étendues sont fonction du niveau, pas au niveau du bloc et de la création d'une fermeture signifie simplement que le cadre englobant est ajoutée à l'environnement lexical du clos de la fonction.

Après que la boucle se termine, la fonction de niveau variable i a la valeur 5, et c'est ce que la fonction interne "voit".


Comme une note de côté: vous devriez vous méfier de fonction inutile de création de l'objet, notamment dans les boucles; il est inefficace, et si les objets DOM sont impliqués, il est facile de créer des références circulaires et donc d'introduire des fuites de mémoire dans Internet Explorer.

9voto

Outlaw Programmer Points 6610

Je pense que cela pourrait être ce que vous voulez:

var closures = [];

function createClosure(i) {
    closures[i] = function() {
        alert("i = " + i);
    };
}

function create() {
    for (var i = 0; i < 5; i++) {
        createClosure(i);
    }
}

9voto

Ionuț G. Stan Points 62482

La solution est d'avoir une auto-exécution lambda d'emballage de votre tableau de pousser. Vous aussi passer, j'ai comme un argument pour que la lambda. La valeur de i à l'intérieur de l'auto-exécution lambda va de l'ombre à la valeur de la je et tout fonctionnera comme prévu:

function create() {
    for (var i = 0; i < 5; i++) (function(i) {
        closures[i] = function() {
            alert("i = " + i);
        };
    })(i);
}

Une autre solution serait de créer encore une autre fermeture qui capte la valeur exacte de i et l'affecte à une autre variable qui serait "coincés" dans le final lambda:

function create() {
    for (var i = 0; i < 5; i++) (function() {
        var x = i;

        closures.push(function() {
            alert("i = " + x);
        });
    })();
}

6voto

Andrew Hare Points 159332

Oui fermetures travaillent ici. Chaque fois que vous la boucle de la fonction vous êtes la création d'empoigne l' i. Chaque fonction, vous pouvez créer des partages le même i. Le problème que vous rencontrez est que, depuis, ils partagent tous le même i ils partagent également la valeur finale de l' i puisque c'est le même capturé variable.

Edit: Cet article par M. Skeet explique fermetures en peu de profondeur et de traite de cette question en particulier d'une manière qui est beaucoup plus instructif alors que j'ai ici. Cependant attention à la façon dont Javascript et C# gérer les fermetures ont quelques différences subtiles. Passez à la section intitulée "Comparer des stratégies de récupération: la complexité vs power" pour son explication sur cette question.

4voto

John Resig de l' Apprentissage Avancé JavaScript explique cela et plus encore. C'est une présentation interactive qui explique beaucoup de choses à propos de JavaScript, et les exemples sont le plaisir de lire et d'exécuter.

Il a un chapitre sur les fermetures, et cet exemple ressemble beaucoup à la vôtre.

Voici le cassé exemple:

var count = 0; 
for ( var i = 0; i < 4; i++ ) { 
  setTimeout(function(){ 
    assert( i == count++, "Check the value of i." ); 
  }, i * 200); 
}

Et le correctif:

var count = 0; 
for ( var i = 0; i < 4; i++ ) (function(i){ 
  setTimeout(function(){ 
    assert( i == count++, "Check the value of i." ); 
  }, i * 200); 
})(i);

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X