Boucle intérieure de fermeture JavaScript – exemple pratique simple

var funcs = [];
for (var i = 0; i < 3; i++) {      // let's create 3 functions
  funcs[i] = function() {          // and store them in funcs
    console.log("My value: " + i); // each should log its value.
  };
}
for (var j = 0; j < 3; j++) {
  funcs[j]();                      // and now let's run each one to see
}

il affiche ceci:

ma valeur: 3

Ma valeur: 3

Ma valeur: 3

alors que j'aimerais qu'il sorte:

ma valeur: 0

Ma valeur: 1

Ma valeur: 2


Le même problème se produit lorsque le retard dans l'exécution de la fonction est causé par l'utilisation d'écouteurs d'événements:

var buttons = document.getElementsByTagName("button");
for (var i = 0; i < buttons.length; i++) {          // let's create 3 functions
  buttons[i].addEventListener("click", function() { // as event listeners
    console.log("My value: " + i);                  // each should log its value.
  });
}
<button>0</button><br>
<button>1</button><br>
<button>2</button>

... ou un code asynchrone, p.ex. utilisant des promesses:

// Some async wait function
const wait = (ms) => new Promise((resolve, reject) => setTimeout(resolve, ms));

for(var i = 0; i < 3; i++){
  wait(i * 100).then(() => console.log(i)); // Log `i` as soon as each promise resolves.
}

Quelle est la solution à ce problème fondamental?

2392
demandé sur Xufox 2009-04-15 10:06:20

30 réponses

Eh bien , le problème est que la variable i , dans chacune de vos fonctions anonymes, est liée à la même variable en dehors de la fonction.

solution Classique: des Fermetures

ce que vous voulez faire est de lier la variable à l'intérieur de chaque fonction à une valeur distincte, immuable en dehors de la fonction:

var funcs = [];

function createfunc(i) {
    return function() { console.log("My value: " + i); };
}

for (var i = 0; i < 3; i++) {
    funcs[i] = createfunc(i);
}

for (var j = 0; j < 3; j++) {
    funcs[j]();                        // and now let's run each one to see
}

Puisqu'il n'y a pas de champ d'application de bloc en JavaScript seulement portée de la fonction - en enveloppant la création de la fonction dans une nouvelle fonction, vous vous assurez que la valeur de "i" reste comme vous l'aviez prévu.


2015 Solution: forEach

avec la disponibilité relativement répandue de la fonction Array.prototype.forEach (en 2015), il est intéressant de noter que dans les situations impliquant itération principalement sur un ensemble de valeurs, .forEach() fournit un moyen propre et naturel d'obtenir une fermeture distincte pour chaque itération. C'est-à-dire, en supposant que vous avez une sorte de tableau contenant des valeurs (références DOM, objets, etc.), et que le problème se pose de configurer des callbacks spécifiques à chaque élément, vous pouvez faire ceci:

var someArray = [ /* whatever */ ];
// ...
someArray.forEach(function(arrayElement) {
  // ... code code code for this one element
  someAsynchronousFunction(arrayElement, function() {
    arrayElement.doSomething();
  });
});

L'idée est que chaque invocation de la fonction de rappel utilisé avec le .forEach boucle sera sa propre fermeture. Le paramètre passé à ce gestionnaire est l'élément de tableau spécifique à cette étape de l'itération. Si elle est utilisée dans un rappel asynchrone, ne pas entrer en collision avec les autres rappels établie à d'autres étapes de l'itération.

si vous travaillez à jQuery, la fonction $.each() vous donne une capacité similaire.


solution ES6: let

ECMAScript 6 (ES6), la version la plus récente de JavaScript, commence maintenant à être mis en œuvre dans de nombreux navigateurs et systèmes d'arrière-plan évolutifs. Il y a aussi des transpilers comme Babel qui convertiront ES6 en ES5 pour permettre l'utilisation de nouvelles fonctionnalités sur les systèmes plus anciens.

ES6 introduit de nouveaux mots clés let et const qui ont une portée différente de celle des variables basées sur var . Par exemple, dans une boucle avec un index basé sur let , chaque itération à travers la boucle aura une nouvelle valeur de i où chaque valeur est scopée à l'intérieur de la boucle, donc votre code fonctionnerait comme vous vous attendez. Il existe de nombreuses ressources, mais je recommande 2ality's block-scoping post comme une excellente source d'information.

for (let i = 0; i < 3; i++) {
    funcs[i] = function() {
        console.log("My value: " + i);
    };
}

attention, cependant, que IE9-IE11 et Edge prior to Edge 14 supportent let mais se trompent au-dessus (ils ne créent pas un nouveau i à chaque fois, donc toutes les fonctions ci-dessus logeraient 3 comme ils le feraient si nous utilisions var ). Edge 14 a enfin raison.

1844
répondu harto 2018-06-06 10:19:14

, Essayez:

var funcs = [];

for (var i = 0; i < 3; i++) {
    funcs[i] = (function(index) {
        return function() {
            console.log("My value: " + index);
        };
    }(i));
}
for (var j = 0; j < 3; j++) {
    funcs[j]();
}

Modifier (2014):

personnellement, je pense que la réponse plus récente de @Aust à propos de l'utilisation de .bind est la meilleure façon de faire ce genre de chose maintenant. Il y a aussi le _.partial de lo-dash/underscore quand vous n'avez pas besoin ou ne voulez pas jouer avec le bind 's thisArg .

346
répondu Bjorn Tipling 2017-05-23 12:18:27

une autre façon qui n'a pas encore été mentionnée est l'utilisation de Function.prototype.bind

var funcs = {};
for (var i = 0; i < 3; i++) {
  funcs[i] = function(x) {
    console.log('My value: ' + x);
  }.bind(this, i);
}
for (var j = 0; j < 3; j++) {
  funcs[j]();
}

mise à JOUR

comme le soulignent @squint et @mekdev, vous obtenez de meilleures performances en créant d'abord la fonction en dehors de la boucle et en liant ensuite les résultats à l'intérieur de la boucle.

function log(x) {
  console.log('My value: ' + x);
}

var funcs = [];

for (var i = 0; i < 3; i++) {
  funcs[i] = log.bind(this, i);
}

for (var j = 0; j < 3; j++) {
  funcs[j]();
}
316
répondu Aust 2018-02-21 16:03:26

utilisant une Expression de fonction immédiatement invoquée , la manière la plus simple et la plus lisible d'enfermer une variable d'index:

for (var i = 0; i < 3; i++) {

    (function(index) {
        console.log('iterator: ' + index);
        //now you can also loop an ajax call here 
        //without losing track of the iterator value: $.ajax({});
    })(i);

}

Cela envoie l'itérateur i dans la fonction anonyme dont nous définissons comme index . Cela crée une fermeture, où la variable i est sauvegardée pour une utilisation ultérieure dans n'importe quelle fonctionnalité asynchrone de L'IIFE.

238
répondu neurosnap 2015-09-22 13:47:05

un peu tard pour le parti, mais j'explorais cette question aujourd'hui et ai remarqué que beaucoup de réponses ne traitent pas complètement la façon dont Javascript traite les scopes, ce qui est essentiellement ce que cela revient à.

comme beaucoup d'autres l'ont mentionné, le problème est que la fonction interne fait référence à la même variable i . Alors pourquoi ne pas simplement créer une nouvelle variable locale à chaque itération, et avoir la référence de la fonction interne à la place?

//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};

var funcs = {};
for (var i = 0; i < 3; i++) {
    var ilocal = i; //create a new local variable
    funcs[i] = function() {
        console.log("My value: " + ilocal); //each should reference its own local variable
    };
}
for (var j = 0; j < 3; j++) {
    funcs[j]();
}

comme avant, où chaque fonction interne produisait la dernière Valeur assignée à i , maintenant chaque fonction interne ne produit que la dernière Valeur assignée à ilocal . Mais chaque itération ne devrait-elle pas avoir son propre ilocal ?

S'avère, c'est le problème. Chaque itération partage la même portée, de sorte que chaque itération après la première ne fait que remplacer ilocal . De MDN :

Important: JavaScript n'a pas de block scope. Les Variables introduites avec un bloc sont scopées à la fonction contenant ou au script, et les effets de leur mise en place persistent au-delà du bloc lui-même. En d'autres termes, les énoncés de bloc n'introduisent pas de portée. Bien que "autonome" blocs sont syntaxe valide, vous ne voulez pas utiliser les blocs autonomes en JavaScript, car ils ne font pas ce que vous pensez qu'ils font, si vous pensez qu'ils font rien comme de tels blocs en C ou en Java.

a répété, pour mettre l'accent:

JavaScript n'a pas de portée de bloc. Les Variables introduites avec un bloc sont scopées à la fonction contenant ou au script

nous pouvons le voir en cochant ilocal avant de le déclarer à chaque itération:

//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};

var funcs = {};
for (var i = 0; i < 3; i++) {
  console.log(ilocal);
  var ilocal = i;
}

C'est exactement pourquoi ce bug est si délicate. Même si vous êtes en train de reclasser une variable, Javascript ne lancera pas d'erreur, et JSLint ne lancera même pas d'avertissement. C'est pourquoi aussi la meilleure façon de résoudre ce problème est de prendre avantage de fermetures, qui est essentiellement l'idée qu'en Javascript, les fonctions internes ont accès à l'extérieur des variables intérieure étendues "enfermer" des étendues extérieures.

Closures

Cela signifie également que les fonctions internes "s'accrocher" extérieur les variables et les garder en vie, même si la fonction externe revient. Pour utiliser ceci, nous créons et appelons une fonction wrapper purement pour faire une nouvelle portée, déclarons ilocal dans la nouvelle portée, et retournons une fonction interne qui utilise ilocal (plus d'Explications ci-dessous):

//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};

var funcs = {};
for (var i = 0; i < 3; i++) {
    funcs[i] = (function() { //create a new scope using a wrapper function
        var ilocal = i; //capture i into a local var
        return function() { //return the inner function
            console.log("My value: " + ilocal);
        };
    })(); //remember to run the wrapper function
}
for (var j = 0; j < 3; j++) {
    funcs[j]();
}

la création de la fonction interne à l'intérieur d'une fonction de wrapper donne à la fonction interne un environnement privé auquel elle seule peut accéder, une "fermeture". Ainsi, chaque temps nous appelons la fonction wrapper nous créons une nouvelle fonction interne avec son propre environnement séparé, en s'assurant que les variables ilocal n'entrent pas en collision et ne s'écrasent pas. Quelques optimisations mineures donnent la réponse finale que beaucoup D'autres utilisateurs ont donnée:

//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};

var funcs = {};
for (var i = 0; i < 3; i++) {
    funcs[i] = wrapper(i);
}
for (var j = 0; j < 3; j++) {
    funcs[j]();
}
//creates a separate environment for the inner function
function wrapper(ilocal) {
    return function() { //return the inner function
        console.log("My value: " + ilocal);
    };
}

mise à Jour

avec ES6 maintenant mainstream, nous pouvons maintenant utiliser le nouveau mot-clé let pour créer bloc-scoped variables:

//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};

var funcs = {};
for (let i = 0; i < 3; i++) { // use "let" to declare "i"
    funcs[i] = function() {
        console.log("My value: " + i); //each should reference its own local variable
    };
}
for (var j = 0; j < 3; j++) { // we can use "var" here without issue
    funcs[j]();
}

regardez comme c'est facile maintenant! Pour plus d'informations voir cette réponse , sur laquelle mes informations sont basées.

133
répondu woojoo666 2018-03-01 22:43:11

avec ES6 maintenant largement soutenu, la meilleure réponse à cette question a changé. ES6 fournit les mots clés let et const pour cette circonstance exacte. Au lieu de jouer avec les fermetures, nous pouvons utiliser let pour définir une variable de portée de boucle comme ceci:

var funcs = [];
for (let i = 0; i < 3; i++) {          
    funcs[i] = function() {            
      console.log("My value: " + i); 
    };
}

val pointera alors vers un objet spécifique à ce tour particulier de la boucle, et retournera la valeur correcte sans la fermeture supplémentaire notation. Cela simplifie évidemment considérablement ce problème.

const est similaire à let avec la restriction supplémentaire que le nom de la variable ne peut pas être rebond à une nouvelle référence après l'assignation initiale.

le soutien de navigateur est maintenant ici pour ceux qui ciblent les dernières versions de navigateurs. const / let sont actuellement pris en charge dans les dernières versions de Firefox, Safari, Edge et Chrome. Il est également pris en charge dans Node, et vous pouvez l'utiliser n'importe où en profitant de construire des outils comme Babel. Vous pouvez voir un exemple de travail ici: http://jsfiddle.net/ben336/rbU4t/2/

Docs ici:

attention, cependant, que IE9-IE11 et bord avant Bord 14 soutien let mais obtenir le ci-dessus tort (ils ne créez pas un nouveau i à chaque fois, donc toutes les fonctions ci-dessus logeraient 3 comme elles le feraient si nous utilisions var ). Edge 14 a enfin raison.

124
répondu Ben McCormick 2018-04-11 17:05:30

une Autre façon de le dire est que le i dans votre fonction est liée au moment de l'exécution de la fonction, pas le temps de création de la fonction.

quand vous créez la fermeture, i est une référence à la variable définie dans le cadre extérieur, pas une copie de celui-ci comme il était quand vous avez créé la fermeture. Elle sera évaluée au moment de l'exécution.

la plupart des autres réponses fournissent des façons de travailler autour de la création une autre variable qui ne changera pas la valeur pour vous.

je voulais juste ajouter une explication pour plus de clarté. Pour une solution, personnellement, J'irais avec Harto's puisque c'est la façon la plus explicite de le faire à partir des réponses ici. Tout le code posté fonctionnera, mais j'opterais pour une usine de fermeture plutôt que d'avoir à écrire une pile de commentaires pour expliquer pourquoi je déclare une nouvelle variable(Freddy et 1800) ou avoir une syntaxe de fermeture intégrée bizarre(apphacker).

78
répondu Darren Clark 2017-12-26 11:07:51

ce que vous devez comprendre est la portée des variables dans javascript est basé sur la fonction. C'est une différence importante que de dire c# où vous avez le champ d'application du bloc, et simplement copier la variable à l'intérieur du for fonctionnera.

L'envelopper dans une fonction qui évalue le retour de la fonction comme la réponse d'apphacker fera l'affaire, car la variable a maintenant la portée de la fonction.

il y a aussi un mot-clé let à la place de var, cela permettrait d'utiliser la règle du champ d'application en bloc. Dans ce cas, définir une variable à l'intérieur du for ferait l'affaire. Cela dit, le mot-clé let n'est pas une solution pratique en raison de la compatibilité.

var funcs = {};
for (var i = 0; i < 3; i++) {
    let index = i;          //add this
    funcs[i] = function() {            
        console.log("My value: " + index); //change to the copy
    };
}
for (var j = 0; j < 3; j++) {
    funcs[j]();                        
}
61
répondu eglasius 2009-04-15 06:31:15

Voici une autre variante de la technique, semblable à celle de Bjorn (apphacker), qui vous permet d'attribuer la valeur de la variable à l'intérieur de la fonction plutôt que de la passer comme paramètre, ce qui pourrait être plus clair parfois:

for (var i = 0; i < 3; i++) {
    funcs[i] = (function() {
        var index = i;
        return function() {
            console.log("My value: " + index);
        }
    })();
}

notez que quelle que soit la technique utilisée, la variable index devient une sorte de variable statique, liée à la copie retournée de la fonction interne. C'est-à-dire: les changements de sa valeur sont conservés entre les appels. Il peut être très pratique.

49
répondu Boann 2012-08-07 08:45:35

décrit l'erreur courante d'utiliser des fermetures en JavaScript.

une fonction définit un nouvel environnement

prendre en considération:

function makeCounter()
{
  var obj = {counter: 0};
  return {
    inc: function(){obj.counter ++;},
    get: function(){return obj.counter;}
  };
}

counter1 = makeCounter();
counter2 = makeCounter();

counter1.inc();

alert(counter1.get()); // returns 1
alert(counter2.get()); // returns 0

pour chaque fois que makeCounter est invoqué, {counter: 0} entraîne la création d'un nouvel objet. Aussi, une nouvelle copie de obj est créé aussi bien pour référencer le nouvel objet. Ainsi, counter1 et counter2 sont indépendants l'un de l'autre.

Fermetures de boucles

utiliser une fermeture dans une boucle est délicat.

prendre en considération:

var counters = [];

function makeCounters(num)
{
  for (var i = 0; i < num; i++)
  {
    var obj = {counter: 0};
    counters[i] = {
      inc: function(){obj.counter++;},
      get: function(){return obj.counter;}
    }; 
  }
}

makeCounters(2);

counters[0].inc();

alert(counters[0].get()); // returns 1
alert(counters[1].get()); // returns 1

Avis counters[0] et counters[1] sont pas indépendant. En fait, ils opèrent sur le même obj !

c'est parce qu'il n'y a qu'une copie de obj partagée entre toutes les itérations de la boucle, peut-être pour des raisons de performance. Quoique {counter: 0} crée un nouvel objet à chaque itération, la même copie de obj sera juste mis à jour avec un référence à l'objet le plus récent.

la Solution est d'utiliser une autre fonction d'assistance:

function makeHelper(obj)
{
  return {
    inc: function(){obj.counter++;},
    get: function(){return obj.counter;}
  }; 
}

function makeCounters(num)
{
  for (var i = 0; i < num; i++)
  {
    var obj = {counter: 0};
    counters[i] = makeHelper(obj);
  }
}

cela fonctionne parce que les variables locales dans la portée de la fonction directement, ainsi que les variables d'argument de la fonction, sont attribuées de nouvelles copies à l'entrée.

pour une discussion détaillée, s'il vous plaît voir JavaScript écueils et usage de la fermeture

45
répondu Lucas 2013-04-20 09:59:57

la solution La plus simple serait,

au lieu d'utiliser:

var funcs = [];
for(var i =0; i<3; i++){
    funcs[i] = function(){
        alert(i);
    }
}

for(var j =0; j<3; j++){
    funcs[j]();
}

qui alerte "2", pour 3 fois. Cela est dû au fait que les fonctions anonymes créées dans For loop partagent la même fermeture, et dans cette fermeture, la valeur de i est la même. Utilisez ceci pour empêcher la fermeture partagée:

var funcs = [];
for(var new_i =0; new_i<3; new_i++){
    (function(i){
        funcs[i] = function(){
            alert(i);
        }
    })(new_i);
}

for(var j =0; j<3; j++){
    funcs[j]();
}

l'idée derrière ceci est, encapsuler le corps entier de la boucle pour avec un IIFE (Expression de fonction immédiatement invoquée) et en passant new_i comme paramètre et en le capturant comme i . Puisque la fonction anonymous est exécutée immédiatement, la valeur i est différente pour chaque fonction définie à l'intérieur de la fonction anonymous.

cette solution semble convenir à n'importe quel tel problème puisqu'elle exigera des changements minimes au code original souffrant de cette question. En fait, c'est par la conception, il ne devrait pas être un problème!

41
répondu Kemal Dağ 2017-12-28 08:07:40

essayez ce plus court

  • aucun tableau

  • pas de supplément pour la boucle



for (var i = 0; i < 3; i++) {
    createfunc(i)();
}

function createfunc(i) {
    return function(){console.log("My value: " + i);};
}

http://jsfiddle.net/7P6EN /

24
répondu yilmazburk 2013-09-19 14:20:24

Le principal problème avec le code indiqué par l'OP, c'est que i n'est jamais lu jusqu'à la deuxième boucle. Pour démontrer, imaginez voir une erreur à l'intérieur du code

funcs[i] = function() {            // and store them in funcs
    throw new Error("test");
    console.log("My value: " + i); // each should log its value.
};

l'erreur ne se produit pas jusqu'à ce que funcs[someIndex] soit exécuté () . En utilisant cette même logique, il devrait être évident que la valeur de i n'est pas non plus collectée avant ce point. Une fois la boucle d'origine terminée, i++ apporte i à la valeur de 3 qui entraîne la défaillance de la condition i < 3 et la fin de la boucle. À ce point, i est 3 et donc quand funcs[someIndex]() est utilisé, et i est évalué, il est 3 - à chaque fois.

Pour contourner cela, vous devez évaluer i comme il est rencontré. Notez que cela s'est déjà produit sous la forme de funcs[i] (où il y a 3 index uniques). Il y a plusieurs façons de capturer cette valeur. L'un est à passer comme paramètre à une fonction qui est montré dans plusieurs façons déjà ici.

une autre option consiste à construire un objet de fonction qui pourra se fermer sur la variable. Qui peut être accompli ainsi

jsFiddle Demo

funcs[i] = new function() {   
    var closedVariable = i;
    return function(){
        console.log("My value: " + closedVariable); 
    };
};
20
répondu Travis J 2014-03-05 23:03:24

Voici une solution simple qui utilise forEach (travaux retour à IE9):

var funcs = {};
[0,1,2].forEach(function(i) {          // let's create 3 functions
    funcs[i] = function() {            // and store them in funcs
        console.log("My value: " + i); // each should log its value.
    };
})
for (var j = 0; j < 3; j++) {
    funcs[j]();                        // and now let's run each one to see
}

Imprime:

My value: 0
My value: 1
My value: 2
19
répondu Daryl 2014-05-03 03:42:57

fonctions JavaScript "fermer" la portée qu'ils ont accès lors de la déclaration, et de conserver l'accès à ce champ d'application, même en tant que variables de ce changement de portée.

var funcs = []

for (var i = 0; i < 3; i += 1) {
  funcs[i] = function () {
    console.log(i)
  }
}

for (var k = 0; k < 3; k += 1) {
  funcs[k]()
}

chaque fonction du tableau ci-dessus se ferme sur la portée globale (globale, simplement parce que c'est la portée dans laquelle elles sont déclarées).

plus tard, ces fonctions sont invoquées en enregistrant la valeur la plus récente de i dans le de portée mondiale. C'est la magie, et la frustration, de la fermeture.

" les fonctions JavaScript se rapprochent de la portée dans laquelle elles sont déclarées et conservent l'accès à cette portée même lorsque des valeurs variables à l'intérieur de cette portée changent."

en utilisant let au lieu de var résout cela en créant une nouvelle portée chaque fois que la boucle for tourne, créant une portée séparée pour chaque fonction à fermer. Diverses autres techniques faire la même chose avec des fonctionnalités supplémentaires.

var funcs = []

for (let i = 0; i < 3; i += 1) {
  funcs[i] = function () {
    console.log(i)
  }
}

for (var k = 0; k < 3; k += 1) {
  funcs[k]()
}

( let ) rend les variables qui sont en bloc scopé au lieu de la fonction scopée. Les blocs sont désignés par des accolades curly, mais dans le cas de la boucle for, la variable d'initialisation, i dans notre cas, est considérée comme déclarée dans les accolades.)

16
répondu Costa 2017-02-15 04:09:52

après avoir lu diverses solutions, j'aimerais ajouter que la raison pour laquelle ces solutions fonctionnent est de s'appuyer sur le concept de chaîne de portée . C'est la façon dont JavaScript résout une variable pendant l'exécution.

  • chaque définition de fonction forme un champ d'application composé de toutes les variables déclarées par var et son arguments .
  • si nous avons une fonction interne définie à l'intérieur d'un autre fonction (externe), ce qui forme une chaîne, et sera utilisé lors de l'exécution
  • Lorsqu'une fonction est exécutée, l'exécution évalue les variables en effectuant une recherche dans la "chaîne scope . Si une variable peut être trouvée dans un certain point de la chaîne, elle cessera de chercher et de l'utiliser, sinon elle continuera jusqu'à ce que la portée globale atteint qui appartient à window .

dans le code initial:

funcs = {};
for (var i = 0; i < 3; i++) {         
  funcs[i] = function inner() {        // function inner's scope contains nothing
    console.log("My value: " + i);    
  };
}
console.log(window.i)                  // test value 'i', print 3

quand funcs sera exécuté, la chaîne scope sera function inner -> global . Comme la variable i ne peut pas être trouvée dans function inner (ni déclarée en utilisant var ni passée comme argument), elle continue à chercher, jusqu'à ce que la valeur de i soit finalement trouvée dans la portée globale qui est window.i .

En l'enveloppant dans une fonction externe soit explicitement définir une fonction d'assistance comme harto a fait ou utiliser un fonction anonyme comme Bjorn :

funcs = {};
function outer(i) {              // function outer's scope contains 'i'
  return function inner() {      // function inner, closure created
   console.log("My value: " + i);
  };
}
for (var i = 0; i < 3; i++) {
  funcs[i] = outer(i);
}
console.log(window.i)          // print 3 still

quand funcs sera exécuté, la chaîne scope sera function inner -> function outer . Ce temps i peut être trouvé dans la portée de la fonction externe qui est exécutée 3 fois dans la boucle for, chaque temps a une valeur i liée correctement. Il n'utilisera pas la valeur de window.i lors de l'exécution intérieure.

plus de détails peuvent être trouvés ici

Il inclut l'erreur courante de créer une boucle dans la boucle comme ce que nous avons ici, ainsi que la raison pour laquelle nous avons besoin de fermeture et la considération de performance.

11
répondu wpding 2017-05-23 12:02:57

avec les nouvelles caractéristiques de la détermination de la portée au niveau du bloc ES6 est géré:

var funcs = [];
for (let i = 0; i < 3; i++) {          // let's create 3 functions
    funcs[i] = function() {            // and store them in funcs
        console.log("My value: " + i); // each should log its value.
    };
}
for (let j = 0; j < 3; j++) {
    funcs[j]();                        // and now let's run each one to see
}

le code dans la question de L'OP est remplacé par let au lieu de var .

10
répondu Prithvi Uppalapati 2017-12-26 20:27:59

je suis surpris que personne n'ait encore suggéré d'utiliser la fonction forEach pour mieux éviter (re)l'utilisation de variables locales. En fait, je n'utilise plus du tout for(var i ...) pour cette raison.

[0,2,3].forEach(function(i){ console.log('My value:', i); });
// My value: 0
// My value: 2
// My value: 3

// modifiées à utiliser forEach au lieu de la carte.

6
répondu Christian Landgren 2017-12-26 19:51:50

tout d'abord, comprendre ce qui ne va pas avec ce code:

var funcs = [];
for (var i = 0; i < 3; i++) {          // let's create 3 functions
    funcs[i] = function() {            // and store them in funcs
        console.log("My value: " + i); // each should log its value.
    };
}
for (var j = 0; j < 3; j++) {
    funcs[j]();                        // and now let's run each one to see
}

ici quand le tableau funcs[] est initialisé, i est incrémenté, le tableau funcs est initialisé et la taille du tableau func devient 3, donc i = 3, . Maintenant quand le funcs[j]() est appelé , il est de nouveau en utilisant la variable i , qui a déjà été incrémenté à 3.

maintenant pour résoudre cela, nous avons beaucoup option. En voici deux:

  1. nous pouvons initialiser i avec let ou initialiser une nouvelle variable index avec let et la rendre égale à i . Ainsi, lorsque l'appel est fait, index sera utilisé et sa portée se terminera après l'initialisation. Et pour l'appel, index sera à nouveau initialisé:

    var funcs = [];
    for (var i = 0; i < 3; i++) {          
        let index = i;
        funcs[i] = function() {            
            console.log("My value: " + index); 
        };
    }
    for (var j = 0; j < 3; j++) {
        funcs[j]();                        
    }
    
  2. une autre Option peut être introduire un tempFunc qui renvoie la fonction actuelle:

    var funcs = [];
    function tempFunc(i){
        return function(){
            console.log("My value: " + i);
        };
    }
    for (var i = 0; i < 3; i++) {  
        funcs[i] = tempFunc(i);                                     
    }
    for (var j = 0; j < 3; j++) {
        funcs[j]();                        
    }
    
5
répondu Ali Kahoot 2017-12-26 22:46:45

cette question montre vraiment L'histoire de JavaScript! Maintenant nous pouvons éviter de bloquer la détermination de la portée avec des fonctions de flèche et manipuler des boucles directement à partir de noeuds DOM en utilisant des méthodes D'objet.

const funcs = [1, 2, 3].map(i => () => console.log(i));
funcs.map(fn => fn())

const buttons = document.getElementsByTagName("button");
Object
  .keys(buttons)
  .map(i => buttons[i].addEventListener('click', () => console.log(i)));
<button>0</button><br>
<button>1</button><br>
<button>2</button>
4
répondu sidhuko 2018-01-13 13:17:57

nous allons vérifier, ce qui se passe réellement quand vous déclarez var et let un par un.

CAS1 : utilisant var

<script>
   var funcs = [];
   for (var i = 0; i < 3; i++) {
     funcs[i] = function () {
        debugger;
        console.log("My value: " + i);
     };
   }
   console.log(funcs);
</script>

ouvrez maintenant votre fenêtre de console chrome en appuyant sur F12 et rafraîchissez la page. Étendre toutes les 3 fonctions à l'intérieur du tableau.Vous verrez un propriété appelée [[Scopes]] .Développez-la. Vous verrez un objet array appelé "Global" , développez celui-là. Vous trouverez une propriété 'i' déclarée dans l'objet qui a une valeur 3.

enter image description here

enter image description here

Conclusion:

  1. quand vous déclarer une variable en utilisant 'var' en dehors d'une fonction, elle devient une variable globale(vous pouvez vérifier en tapant i ou window.i dans la fenêtre de la console.It will return 3).
  2. la fonction que vous avez déclarée n'appellera pas et ne vérifiera pas la valeur à l'intérieur de la fonction à moins que vous n'invoquiez la fonction.
  3. lorsque vous invoquez la fonction, console.log("My value: " + i) prend la valeur de son objet Global et affiche la résultat.

CASE2: utilisation de let

remplacer 'var' par 'let'

<script>
    var funcs = [];
    for (let i = 0; i < 3; i++) {
        funcs[i] = function () {
           debugger;
           console.log("My value: " + i);
        };
    }
    console.log(funcs);
</script>

faites la même chose, allez à la portée . Maintenant, vous verrez deux objets "Block" et "Global" . Maintenant, étendez l'objet Block , vous va voir 'i' est défini là , et la chose étrange est que , pour chaque fonction , la valeur si i est différente (0, 1, 2).

enter image description here

Conclusion:

lorsque vous déclarez la variable en utilisant 'let' même en dehors de la fonction mais à l'intérieur de la boucle , cette variable ne sera pas un Global variable , il deviendra un Block niveau variable qui n'est disponible que pour la même fonction.C'est la raison , nous obtiennent la valeur de i différente pour chaque fonction quand on invoque les fonctions.

pour plus de détails sur la façon dont plus proche fonctionne , s'il vous plaît passer par le tutoriel vidéo impressionnant https://youtu.be/71AtaJpJHw0

4
répondu Bimal Das 2018-01-16 14:29:57

la raison pour laquelle votre exemple original n'a pas fonctionné est que toutes les fermetures que vous avez créées dans la boucle Référencé le même cadre. En effet, ayant 3 méthodes sur un objet avec une seule variable i . Ils ont tous imprimé la même valeur.

3
répondu jottos 2017-12-26 20:27:18

utiliser fermeture structure, ce qui réduirait votre supplément pour la boucle. Vous pouvez le faire en boucle simple:

var funcs = [];
for (var i = 0; i < 3; i++) {     
  (funcs[i] = function() {         
    console.log("My value: " + i); 
  })(i);
}
3
répondu Vikash Singh 2017-12-27 00:06:45

je préfère utiliser forEach fonction, qui a sa propre fermeture avec la création d'une pseudo gamme:

var funcs = [];

new Array(3).fill(0).forEach(function (_, i) { // creating a range
    funcs[i] = function() {            
        // now i is safely incapsulated 
        console.log("My value: " + i);
    };
});

for (var j = 0; j < 3; j++) {
    funcs[j](); // 0, 1, 2
}

qui semble plus laid que les gammes dans d'autres langues, mais IMHO moins monstrueux que d'autres solutions.

2
répondu Rax Wunter 2015-12-17 15:14:47

vous pouvez utiliser un module déclaratif pour les listes de données telles que query-js (*). Dans ces situations, je trouve personnellement une approche déclarative moins surprenante

var funcs = Query.range(0,3).each(function(i){
     return  function() {
        console.log("My value: " + i);
    };
});

Vous pouvez alors utiliser votre deuxième boucle et d'obtenir le résultat attendu ou vous pourriez le faire

funcs.iterate(function(f){ f(); });

( * ) je suis l'auteur de query-js et donc biaisé vers l'utilisation, alors ne prenez pas mes mots comme une recommandation pour ladite bibliothèque seulement pour le approche déclarative:)

1
répondu Rune FS 2015-10-27 10:15:11

et encore une autre solution: au lieu de créer une autre boucle, liez simplement le this à la fonction de retour.

var funcs = [];

function createFunc(i) {
  return function() {
    console.log('My value: ' + i); //log value of i.
  }.call(this);
}

for (var i = 1; i <= 5; i++) {  //5 functions
  funcs[i] = createFunc(i);     // call createFunc() i=5 times
}

en liant ceci, résout également le problème.

1
répondu pixel 67 2017-08-28 16:37:06

beaucoup de solutions semblent correctes, mais elles ne mentionnent pas qu'il est appelé Currying qui est un modèle de conception de programmation fonctionnelle pour des situations comme ici. De 3 à 10 fois plus rapide que lient selon le navigateur.

var funcs = [];
for (var i = 0; i < 3; i++) {      // let's create 3 functions
  funcs[i] = curryShowValue(i);
}
for (var j = 0; j < 3; j++) {
  funcs[j]();                      // and now let's run each one to see
}

function curryShowValue(i) {
  return function showValue() {
    console.log("My value: " + i);
  }
}

Voir le gain de performance dans les différents navigateurs .

1
répondu Pawel 2017-12-26 21:21:57

votre code ne fonctionne pas, parce que ce qu'il fait est:

Create variable `funcs` and assign it an empty array;  
Loop from 0 up until it is less than 3 and assign it to variable `i`;
    Push to variable `funcs` next function:  
        // Only push (save), but don't execute
        **Write to console current value of variable `i`;**

// First loop has ended, i = 3;

Loop from 0 up until it is less than 3 and assign it to variable `j`;
    Call `j`-th function from variable `funcs`:  
        **Write to console current value of variable `i`;**  
        // Ask yourself NOW! What is the value of i?

maintenant la question Est, Quelle est la valeur de la variable i quand la fonction est appelée? Parce que la première boucle est créée avec la condition de i < 3 , elle s'arrête immédiatement quand la condition est fausse, donc elle est i = 3 .

vous devez comprendre que, au moment où vos fonctions sont créées, aucun de leur code n'est exécuté, il est seulement enregistré pour plus tard. Et donc, quand ils sont appelés plus tard, l'interprète exécute et demande: "Quelle est la valeur actuelle de i ?"

donc, votre but est d'abord de sauver la valeur de i pour fonctionner et seulement après que sauver la fonction à funcs . Cela pourrait être fait par exemple de cette façon:

var funcs = [];
for (var i = 0; i < 3; i++) {          // let's create 3 functions
    funcs[i] = function(x) {            // and store them in funcs
        console.log("My value: " + x); // each should log its value.
    }.bind(null, i);
}
for (var j = 0; j < 3; j++) {
    funcs[j]();                        // and now let's run each one to see
}

de cette façon, chaque fonction aura sa propre variable x et nous définissons cette x à la valeur de i dans chaque itération.

Ce n'est qu'une des multiples façons de résoudre ce problème.

1
répondu Buksy 2017-12-26 22:53:29
var funcs = [];
for (var i = 0; i < 3; i++) {      // let's create 3 functions
  funcs[i] = function(param) {          // and store them in funcs
    console.log("My value: " + param); // each should log its value.
  };
}
for (var j = 0; j < 3; j++) {
  funcs[j](j);                      // and now let's run each one to see with j
}
1
répondu ashish yadav 2018-07-13 08:02:09

COUNTER BEING A PRIMITIVE

définissons les fonctions de rappel comme suit:

// ****************************
// COUNTER BEING A PRIMITIVE
// ****************************
function test1() {
    for (var i=0; i<2; i++) {
        setTimeout(function() {
            console.log(i);
        });
    }
}
test1();
// 2
// 2

après timeout complète, il affichera 2 pour les deux. C'est parce que la fonction callback accède à la valeur basée sur la portée lexicale , où elle était fonction a été définie.

pour passer et préserver la valeur alors que le rappel a été défini, nous pouvons créer un fermeture , pour préserver la valeur avant que le callback ne soit invoqué. Cela peut se faire comme suit:

function test2() {
    function sendRequest(i) {
        setTimeout(function() {
            console.log(i);
        });
    }

    for (var i = 0; i < 2; i++) {
        sendRequest(i);
    }
}
test2();
// 1
// 2

maintenant ce qui est spécial à ce sujet est que "les primitives sont passées par valeur et copiées. Ainsi, lorsque la fermeture est définie, ils gardent la valeur de la précédente boucle."

COUNTER BEING AN OBJECT

puisque les fermetures ont accès aux variables de la fonction de parent par référence, cette approche serait différent de celui des primitifs.

// ****************************
// COUNTER BEING AN OBJECT
// ****************************
function test3() {
    var index = { i: 0 };
    for (index.i=0; index.i<2; index.i++) {
        setTimeout(function() {
            console.log('test3: ' + index.i);
        });
    }
}
test3();
// 2
// 2

ainsi, même si une fermeture est créée pour la variable étant passée comme un objet, la valeur de l'indice de boucle ne sera pas préservée. Ceci montre que les valeurs d'un objet ne sont pas copiées alors qu'elles sont accessibles par référence.

function test4() {
    var index = { i: 0 };
    function sendRequest(index, i) {
        setTimeout(function() {
            console.log('index: ' + index);
            console.log('i: ' + i);
            console.log(index[i]);
        });
    }

    for (index.i=0; index.i<2; index.i++) {
        sendRequest(index, index.i);
    }
}
test4();
// index: { i: 2}
// 0
// undefined

// index: { i: 2}
// 1
// undefined
0
répondu jsbisht 2017-12-27 00:01:42