web-dev-qa-db-fra.com

Fermeture JavaScript dans les boucles - exemple pratique simple

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

Il génère ceci:

Ma valeur: 3
Ma valeur: 3
Ma valeur: 3

Considérant que je voudrais que cela produise:

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 provoqué par l'utilisation de programmes d'écoute d'événements:

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

… Ou un code asynchrone, par exemple en utilisant des promesses:

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

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

Quelle est la solution à ce problème fondamental?

2641
nickf

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: fermetures

Ce que vous voulez faire, c'est lier la variable de chaque fonction à une valeur distincte et 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++) {
  // and now let's run each one to see
  funcs[j]();
}

Etant donné qu'il n'y a pas de portée de bloc dans JavaScript - portée de fonction uniquement - en encapsulant la création de fonction dans une nouvelle fonction, vous vous assurez que la valeur de "i" reste telle que vous le souhaitiez.


Solution 2015: pour chaque

Avec la disponibilité relativement répandue de la fonction Array.prototype.forEach (en 2015), il convient de noter que dans les situations impliquant une itération principalement sur un tableau de valeurs, .forEach() fournit un moyen propre et naturel d'obtenir une fermeture distincte. pour chaque itération. En d’autres termes, en supposant que vous disposiez d’une sorte de tableau contenant des valeurs (références DOM, objets, etc.) et que le problème de la configuration des rappels spécifiques à chaque élément se pose, vous pouvez procéder comme suit:

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 appel de la fonction de rappel utilisée avec la boucle .forEach aura sa propre fermeture. Le paramètre transmis à ce gestionnaire est l'élément de tableau spécifique à cette étape particulière de l'itération. S'il est utilisé dans un rappel asynchrone, il n'entrera en collision avec aucun des autres rappels établis à d'autres étapes de l'itération.

Si vous travaillez avec jQuery, la fonction $.each() vous offre une capacité similaire.


Solution ES6: let

ECMAScript 6 (ES6) introduit les nouveaux mots-clés let et const dont la portée est différente de celle des variables basées sur var. Par exemple, dans une boucle avec un index basé sur let, chaque itération de la boucle aura une nouvelle valeur de i où chaque valeur est délimitée à l'intérieur de la boucle, de sorte que votre code fonctionne comme prévu. . Il existe de nombreuses ressources, mais je vous recommanderais le post de portée de bloc de 2ality comme une excellente source d'informations.

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

Attention, IE9-IE11 et Edge antérieurs à Edge 14 prennent en charge let, mais ne répondent pas correctement à la description ci-dessus (ils ne créent pas de nouvelle i à chaque fois. ils le feraient si nous utilisions var). Edge 14 réussit enfin.

2036
harto

Essayer:

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

Éditer (2014):

Personnellement, je pense que @ Aust réponse plus récente concernant l’utilisation de .bind est la meilleure façon de faire ce genre de choses maintenant. Il y a aussi _.partial lorsque vous n'avez pas besoin de vouloir jouer avec bind de thisArg.

365
Bjorn Tipling

Une autre manière 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]();
}_

UPDATE

Comme l'ont souligné @squint et @mekdev, vous obtenez de meilleures performances en créant d'abord la fonction en dehors de la boucle, puis en liant les résultats dans 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]();
}_
336
Aust

Utilisation de expression de fonction immédiatement appelée , le moyen le plus simple et le 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);

}

Ceci envoie l'itérateur i dans la fonction anonyme que nous définissons comme index. Cela crée une fermeture, dans laquelle la variable i est enregistrée pour une utilisation ultérieure dans toute fonctionnalité asynchrone au sein du IIFE.

258
neurosnap

Un peu tard pour le parti, mais j’explorais ce problème aujourd’hui et j’ai remarqué que beaucoup de réponses ne traitent pas complètement de la façon dont Javascript traite les portées, ce qui est essentiellement ce à quoi 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 laisser la fonction interne y faire référence?

//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]();
}

Tout comme auparavant, où chaque fonction interne produisait la dernière valeur affectée à i, chaque fonction interne ne produisait que la dernière valeur affectée à ilocal. Mais chaque itération ne devrait-elle pas avoir sa propre ilocal?

Il s'avère que c'est le problème. Chaque itération partage la même portée, donc chaque itération après la première remplace simplement ilocal. De MDN :

Important: JavaScript n'a pas de portée de blocage. Les variables introduites par un bloc sont étendues à la fonction ou au script qui les contient et les effets de leur définition persistent au-delà du bloc lui-même. En d'autres termes, les instructions de bloc n'introduisent pas de portée. Bien que les blocs "autonomes" constituent une syntaxe valide, vous ne souhaitez pas utiliser de blocs autonomes en JavaScript, car ils ne font pas ce que vous pensez qu'ils font, si vous pensez qu'ils agissent de la même manière que ces blocs en C ou en Java.

A réitéré pour souligner:

JavaScript n'a pas de portée de bloc. Les variables introduites avec un bloc sont étendues à la fonction ou au script qui les contient

Nous pouvons le voir en vérifiant 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 difficile. Même si vous redéclarez une variable, Javascript ne génère pas d'erreur, et JSLint ne génère même pas d'avertissement. C’est aussi pourquoi le meilleur moyen de résoudre ce problème consiste à tirer parti des fermetures. C’est essentiellement l’idée que, en Javascript, les fonctions internes ont accès aux variables externes, car les étendues internes "englobent" les étendues externes.

Closures

Cela signifie également que les fonctions internes "conservent" les variables externes et les maintiennent en vie, même si la fonction externe revient. Pour utiliser cela, nous créons et appelons une fonction wrapper uniquement pour créer une nouvelle portée, déclarons ilocal dans la nouvelle portée et renvoyons 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 dans une fonction wrapper donne à la fonction interne un environnement privé auquel elle seule peut accéder, une "fermeture". Ainsi, chaque fois que nous appelons la fonction wrapper, nous créons une nouvelle fonction interne avec son propre environnement, en veillant à ce que les variables ilocal ne se croisent pas et ne se écrasent pas. Quelques optimisations mineures donnent la réponse finale donnée par de nombreux autres SO utilisateurs:

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

Mettre à jour

Avec ES6, désormais grand public, nous pouvons maintenant utiliser le nouveau mot clé let pour créer des variables de type bloc:

//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 this answer , sur lequel mes informations sont basées.

153
woojoo666

Avec ES6 maintenant largement pris en charge, la meilleure réponse à cette question a changé. ES6 fournit les mots-clés let et const pour cette circonstance exacte. Au lieu de déconner avec les fermetures, nous pouvons simplement utiliser let pour définir une variable d'étendue de boucle comme celle-ci:

var funcs = [];

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

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

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

La prise en charge des navigateurs est maintenant disponible pour ceux qui ciblent les dernières versions de navigateurs. const/let sont actuellement pris en charge par les derniers Firefox, Safari, Edge et Chrome. Il est également pris en charge dans Node et vous pouvez l’utiliser n’importe où en tirant parti d’outils de construction tels que Babel. Vous pouvez voir un exemple de travail ici: http://jsfiddle.net/ben336/rbU4t/2/

Docs ici:

Attention, IE9-IE11 et Edge antérieurs à Edge 14 prennent en charge let, mais ne répondent pas correctement à la description ci-dessus (ils ne créent pas de nouvelle i à chaque fois. ils le feraient si nous utilisions var). Edge 14 réussit enfin.

142
Ben McCormick

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

Lorsque vous créez la fermeture, i est une référence à la variable définie dans la portée externe, pas une copie de celle-ci telle qu'elle était lors de la création de la fermeture. Il sera évalué au moment de l'exécution.

La plupart des autres réponses fournissent des moyens de contourner le problème en créant une autre variable qui ne changera pas la valeur pour vous.

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

85
Darren Clark

Ce que vous devez comprendre, c'est que la portée des variables en javascript est basée sur la fonction. Ceci est une différence importante par rapport à c # où vous avez une portée de bloc, et le simple fait de copier la variable dans un à l'intérieur de for fonctionnera.

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

Il existe également un mot-clé let au lieu de var, qui permettrait d'utiliser la règle de portée de bloc. Dans ce cas, définir une variable dans le 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]();
}
69
eglasius

Voici une autre variante de la technique, similaire à celle de Bjorn (apphacker), qui vous permet d'affecter la valeur de variable à l'intérieur de la fonction plutôt que de la transmettre en tant que paramètre, ce qui peut parfois être plus clair:

var funcs = [];
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 que vous utilisez, la variable index devient une sorte de variable statique liée à la copie renvoyée de la fonction interne. C'est-à-dire que les modifications de sa valeur sont préservées entre les appels. Cela peut être très pratique.

57
Boann

Ceci décrit l’erreur commune lors de l’utilisation de fermetures en JavaScript.

Une fonction définit un nouvel environnement

Considérer:

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

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

Boucles dans les boucles

Utiliser une fermeture dans une boucle est délicat.

Considérer:

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

Notez que counters[0] et counters[1] sont non indépendants. En fait, ils fonctionnent sur le même obj!

En effet, il n'y a qu'une seule copie de obj partagée entre toutes les itérations de la boucle, peut-être pour des raisons de performances. Même si {counter: 0} crée un nouvel objet à chaque itération, la même copie de obj sera simplement mise à jour avec une référence à l'objet le plus récent.

La solution consiste à 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 car les variables locales dans l'étendue de la fonction directement, ainsi que les variables d'argument de fonction, se voient attribuer de nouvelles copies lors de la saisie.

Pour une discussion détaillée, veuillez consulter pièges de la fermeture JavaScript et utilisation

51
Mave

La solution la plus simple serait,

À la place 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. En effet, les fonctions anonymes créées dans la boucle for partagent la même fermeture et, dans cette fermeture, la valeur de i est identique. 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 est d'encapsuler tout le corps de la boucle for avec une IIFE (expression de fonction immédiatement invoquée) et de passer new_i en tant que paramètre et de le capturer sous la forme i. La fonction anonyme étant exécutée immédiatement, la valeur i est différente pour chaque fonction définie dans la fonction anonyme.

Cette solution semble convenir à tout problème de ce type car elle nécessitera des modifications minimes du code d'origine affecté par ce problème. En fait, ceci est voulu, cela ne devrait pas être un problème du tout!

48
Kemal Dağ

essayez celui-ci plus court

  • pas de 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/

30
yilmazburk

Voici une solution simple qui utilise forEach (fonctionne de nouveau à 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
}

Impressions:

My value: 0
My value: 1
My value: 2
26
Daryl

Le problème principal avec le code affiché par l'OP est que i n'est jamais lu avant 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.
};
_

En réalité, l'erreur ne se produit pas tant que _funcs[someIndex]_ n'est pas 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++_ ramène i à la valeur _3_, ce qui entraîne l'échec de la condition _i < 3_ et la fin de la boucle. À ce stade, i est _3_ et ainsi lorsque funcs[someIndex]() est utilisé et que i est évalué, il est égal à 3 - à chaque fois.

Pour surmonter cela, vous devez évaluer i au fur et à mesure que vous le rencontrez. Notez que cela s'est déjà produit sous la forme de _funcs[i]_ (où il existe 3 index uniques). Il existe plusieurs façons de capturer cette valeur. La première consiste à le transmettre en tant que paramètre à une fonction qui apparaît déjà de plusieurs manières.

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

jsFiddle Demo

_funcs[i] = new function() {   
    var closedVariable = i;
    return function(){
        console.log("My value: " + closedVariable); 
    };
};
_
26
Travis J

Les fonctions JavaScript "ferment" sur la portée à laquelle elles ont accès lors de la déclaration et conservent l'accès à cette portée même lorsque les variables de cette étendue changent.

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 ont été déclarées).

Ensuite, ces fonctions sont appelées en enregistrant la valeur la plus récente de i dans la portée globale. C'est la magie et la frustration de la fermeture.

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

L'utilisation de let au lieu de var résout ce problème en créant une nouvelle portée chaque fois que la boucle for s'exécute, créant une portée séparée pour chaque fonction à fermer. Diverses autres techniques font la même chose avec des fonctions 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 le champ des variables variable. Les blocs sont désignés par des accolades, 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.)

22
Costa

Après avoir lu diverses solutions, je voudrais ajouter que la raison pour laquelle ces solutions fonctionnent est de s’appuyer sur le concept de chaîne d’étendue . C'est la façon dont JavaScript résout une variable lors de l'exécution.

  • Chaque définition de fonction forme une étendue composée de toutes les variables locales déclarées par var et ses arguments.
  • Si nous avons une fonction interne définie dans une autre fonction (externe), cela forme une chaîne et sera utilisé lors de l'exécution
  • Lorsqu'une fonction est exécutée, le moteur d'exécution évalue les variables en recherchant la chaîne de portées . Si une variable peut être trouvée à un certain point de la chaîne, elle cessera de chercher et de l'utiliser, sinon elle continuera jusqu'à la portée globale atteinte 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

Lorsque funcs est exécuté, la chaîne d'étendue devient function inner -> global. Comme la variable i ne peut pas être trouvée dans function inner (ni déclarée à l'aide de var ni passée en argument), la recherche continue 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, définissez explicitement une fonction d'assistance telle que harto did ou utilisez une fonction anonyme telle que Bjorn a:

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

Lorsque funcs est exécuté, la chaîne de la portée sera désormais function inner -> function outer. Cette fois i peut être trouvé dans la portée de la fonction externe qui est exécutée 3 fois dans la boucle for, chaque fois a la valeur i liée correctement. Il n'utilisera pas la valeur de window.i lors de l'exécution interne.

Plus de détails peuvent être trouvés ici
Cela inclut l’erreur commune dans la création de la clôture dans la boucle, ainsi que la raison pour laquelle nous avons besoin de la clôture et de la performance.

13
wpding

Avec les nouvelles fonctionnalités de l'ES6, la portée au niveau du bloc est gérée:

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 de la question de OP est remplacé par let au lieu de var.

13

Je suis surpris que personne n'ait encore suggéré d'utiliser la fonction forEach pour mieux éviter de (ré) utiliser des 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é pour utiliser forEach au lieu de map.

10
Christian Landgren

Cette question montre vraiment l'histoire de JavaScript! Maintenant, nous pouvons éviter la portée des blocs avec les fonctions de flèche et gérer les boucles directement à partir de nœuds DOM à l'aide de méthodes Object.

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>
9
sidhuko

Tout d’abord, comprenez 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, lorsque le tableau funcs[] est initialisé, i est incrémenté, le tableau funcs est initialisé et la taille du tableau func devient 3, de sorte que i = 3, . Désormais, lorsque la funcs[j]() est appelée, elle utilise à nouveau la variable i, qui a déjà été incrémentée à 3.

Maintenant, pour résoudre cela, nous avons beaucoup d'options. En voici deux:

  1. Nous pouvons initialiser i avec let ou initialiser une nouvelle variable index avec let et la rendre égale à i. Ainsi, lors de l'appel, index sera utilisé et sa portée se terminera après l'initialisation. Et pour appeler, 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 d’introduire un tempFunc qui renvoie la fonction réelle:

    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]();                        
    }
    
8
Ali Kahoot

La raison pour laquelle votre exemple initial n'a pas fonctionné est que toutes les fermetures que vous avez créées dans la boucle faisaient référence au même cadre. En effet, avoir 3 méthodes sur un objet avec une seule variable i. Ils ont tous imprimé la même valeur.

8
jottos

Utilisez la structure fermeture , cela réduirait votre surplus de boucle. Vous pouvez le faire en une seule boucle for:

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

Nous vérifierons ce qui se passe réellement lorsque vous déclarez var et let un par un.

Cas1 : en 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 actualisez la page. Dépensez toutes les 3 fonctions dans le tableau. Vous verrez une propriété appelée [[Scopes]]. Développez-la. Vous verrez un objet de tableau appelé "Global", développez-le. Vous trouverez une propriété 'i' déclarée dans l'objet ayant la valeur 3.

enter image description here

enter image description here

Conclusion:

  1. Lorsque vous déclarez une variable à l'aide de 'var' en dehors d'une fonction, elle devient une variable globale (vous pouvez vérifier en saisissant i ou window.i dans la fenêtre de la console. La valeur renvoyée sera 3).
  2. La fonction anormale que vous avez déclarée n'appelle pas et ne vérifie pas la valeur à l'intérieur de la fonction, sauf si vous appelez les fonctions.
  3. Lorsque vous appelez la fonction, console.log("My value: " + i) prend la valeur de son objet Global et affiche le résultat.

CASE2: utiliser let

Maintenant, remplacez le '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. Vous verrez maintenant deux objets "Block" et "Global". Développez maintenant l'objet Block, vous verrez que "i" est défini ici et ce qui est étrange, c'est que, pour toutes les fonctions, la valeur si i est différente (0, 1, 2).

enter image description here

Conclusion:

Lorsque vous déclarez une variable à l'aide de 'let' même en dehors de la fonction mais à l'intérieur de la boucle, cette variable ne sera pas une variable globale; elle deviendra une variable de niveau Block qui n'est disponible que pour la même fonction uniquement. C’est la raison pour laquelle nous obtenons une valeur de i différente pour chaque fonction lorsque nous appelons les fonctions.

Pour plus de détails sur le rapprochement, consultez le didacticiel vidéo génial https://youtu.be/71AtaJpJHw

7
Bimal Das

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

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
}

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

4
Rax Wunter

Et encore une autre solution: au lieu de créer une autre boucle, il suffit de lier la 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 this , résout également le problème.

4
pixel 67

Vous pouvez utiliser un module déclaratif pour des listes de données telles que query-js (*). Personnellement, dans ces situations, je trouve une approche déclarative moins surprenante

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

Vous pouvez ensuite utiliser votre deuxième boucle et obtenir le résultat attendu ou le faire

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

(*) Je suis l'auteur de query-js et je suis donc partisan de l'utiliser, alors ne prenez pas mes mots pour recommandation pour cette bibliothèque uniquement pour l'approche déclarative :)

3
Rune FS

Votre code ne fonctionne pas, car 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 lorsque la fonction est appelée? Comme la première boucle est créée avec la condition i < 3, elle s’arrête immédiatement lorsque la condition est fausse. Il s’agit donc de i = 3.

Vous devez comprendre que, lorsque vos fonctions sont créées, aucun de leurs codes n’est exécuté, il n’est enregistré que pour plus tard. Et quand ils sont appelés plus tard, l'interprète les exécute et demande: "Quelle est la valeur actuelle de i?"

Votre objectif est donc d’abord de sauvegarder la valeur de i pour qu’elle fonctionne, puis d’enregistrer la fonction sur 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éfinirons cette x sur la valeur de i à chaque itération.

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

3
Buksy
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
}
3
ashish yadav

De nombreuses solutions semblent correctes, mais elles ne mentionnent pas qu'il s'appelle Currying , qui est un modèle de programmation fonctionnel pour des situations comme celle-ci. 3 à 10 fois plus rapide que bind, 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 différents navigateurs .

3
Pawel

Utilisez let (block-scope) au lieu de var.

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

C'est un problème souvent rencontré avec le code asynchrone, la variable i est mutable et à l'heure à laquelle l'appel de fonction est effectué, le code utilisant i sera exécuté et i aura muté. jusqu'à sa dernière valeur, ce qui signifie que toutes les fonctions créées dans la boucle créeront un fermeture et i sera égal à 3 (la limite supérieure + 1 de la boucle for.

Une solution consiste à créer une fonction qui contiendra la valeur de i pour chaque itération et forcera une copie i (comme il s’agit d’une primitive, considérez-la comme un instantané si cela vous aide. ).

2
axelduch

Il suffit de changer le mot clé var à laisser.

var est une fonction étendue.

let est bloqué portée.

Lorsque vous démarrez votre code, la boucle for effectuera une itération et affectera la valeur de i à 3, qui restera 3 tout au long de votre code. Je vous suggère de lire plus sur les portées dans node (let, var, const et autres)

funcs = [];
for (let i = 0; i < 3; i++) {      // let's create 3 functions
  funcs[i] =async function() {          // and store them in funcs
    await 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
}
1
Nouman Dilshad

COMPTEUR ÊTRE PRIMITIF

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

Une fois le délai expiré, il en imprimera 2 pour les deux. Cela est dû au fait que la fonction de rappel accède à la valeur en fonction de portée lexicale , où la fonction était définie.

Pour transmettre et conserver la valeur pendant que le rappel a été défini, nous pouvons créer un fermeture , afin de conserver la valeur avant que le rappel ne soit appelé. Cela peut être fait 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, c’est "Les primitives sont passées par valeur et copiées. Ainsi, lorsque la fermeture est définie, elles conservent la valeur de la boucle précédente."

COMPTEUR ÊTRE UN OBJET

Comme les fermetures ont accès aux variables de la fonction parent via référence, cette approche serait différente de celle utilisée pour les primitives.

// ****************************
// 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 transmise en tant qu'objet, la valeur de l'index de la boucle ne sera pas conservée. Cela montre que les valeurs d'un objet ne sont pas copiées alors qu'elles sont accessibles via une 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
1
jsbisht

Cela prouve à quel point javascript est moche en ce qui concerne le fonctionnement de la "fermeture" et de la "non-fermeture".

Dans le cas de:

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

funcs [i] est une fonction globale et 'console.log ("Ma valeur:" + i);' est l'impression de la variable globale i

Dans le cas de

var funcs = [];

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

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

en raison de cette conception de fermeture tordue de javascript, 'console.log ("Ma valeur:" + i);' est en train d'imprimer le i à partir de la fonction externe 'createfunc (i)'

tout cela parce que javascript ne peut pas concevoir quelque chose de convenable comme la variable 'statique' dans une fonction comme ce que fait le langage de programmation C!

0
user1559625

Jusqu'à ES5, ce problème ne peut être résolu qu'en utilisant fermeture.

Mais maintenant, dans ES6, nous avons des variables de portée de niveau de bloc. Changer var en let en premier pour la boucle résoudra le problème.

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 (var j = 0; j < 3; j++) {
  funcs[j]();                      // and now let's run each one to see
}
0
Shivang Gupta

Avec le support de ES6, la meilleure solution consiste à utiliser les mots-clés let et const pour cette circonstance exacte. Donc, var variable get hoisted et avec la fin de la boucle, la valeur de i est mise à jour pour tous les closures..., on peut simplement utiliser let pour définir une variable d’étendue de boucle comme ceci:

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

Profitons de new Function . Ainsi, i cesse d'être une variable de fermeture et devient simplement une partie du texte:

_var funcs = [];
for (var i = 0; i < 3; i++) {
    var functionBody = 'console.log("My value: ' + i + '");';
    funcs[i] = new Function(functionBody);
}

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

Disons que vous n'utilisez pas es6; Vous pouvez utiliser la fonction IFFY:

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

Mais ce sera différent.

0
Eyal Segal

Alors que cette question est ancienne et a répondu, j'ai encore une autre solution assez intéressante:

var funcs = [];

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

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

Le changement est si petit qu'il est presque difficile de voir ce que j'ai fait. J'ai changé le deuxième itérateur d'un j à un i. Cela rafraîchit l’état de i à temps pour vous donner le résultat souhaité. Je l’ai fait par accident, mais c’est logique compte tenu des réponses précédentes.

J'ai écrit ceci pour souligner cette petite mais très importante différence. J'espère que cela aidera à dissiper une certaine confusion chez d'autres apprenants comme moi.

Note: Je ne partage pas ceci parce que je pense que c'est la bonne réponse. C'est une solution floconneuse qui va probablement casser dans certaines circonstances. En fait, je suis assez surpris que cela fonctionne vraiment.

0
Brooks DuBois
  asyncIterable = [1,2,3,4,5,6,7,8];

  (async function() {
       for await (let num of asyncIterable) {
         console.log(num);
       }
    })();
0
swogger

D'accord. J'ai lu toutes les réponses. Même s'il y a une bonne explication ici - je ne pouvais tout simplement pas que cela fonctionne. Alors je suis allé chercher sur internet. La personne à https://dzone.com/articles/why-does-javascript-loop-only-use-last-value avait une réponse qui n'est pas présentée ici. Alors j'ai pensé poster un court exemple. Cela me semblait beaucoup plus logique.

En bref, la commande LET est Nice mais elle est utilisée maintenant seulement. CEPENDANT, la commande LET n’est en réalité qu’un combo TRY-CATCH. Cela fonctionne tout le chemin de retour à IE3 (je crois). Utiliser le combo TRY-CATCH - la vie est simple et agréable. Probablement pourquoi les gens d'EMCScript ont décidé de l'utiliser. Il n’a pas non plus besoin d’une fonction setTimeout (). Donc, pas de temps perdu. Fondamentalement, vous avez besoin d’un combo TRY-CATCH par boucle FOR. Voici un exemple:

    for( var i in myArray ){
       try{ throw i }
       catch(ii){
// Do whatever it is you want to do with ii
          }
       }

Si vous avez plus d'une boucle FOR, vous venez de mettre un combo TRY-CATCH pour chaque boucle. De plus, personnellement, j'utilise toujours la double lettre de la variable FOR que j'utilise. Donc, "ii" pour "i" et ainsi de suite. J'utilise cette technique dans une routine pour envoyer des commandes mouseover à une autre routine.

0
Mark Manning