tissage - Fermeture JavaScript dans les boucles-exemple pratique simple




medium closure javascript (20)

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 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");
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 du code asynchrone, par exemple en utilisant Promises:

// 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?


Nous vérifierons ce qui se passe réellement lorsque vous déclarez varet letun par un.

Cas 1 : en utilisantvar

<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 la fenêtre de votre 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]].Expand celle-là. Vous verrez un objet de tableau appelé "Global", développez-le. Vous trouverez une propriété 'i'déclarée dans l'objet qui aura la valeur 3.

Conclusion:

  1. Lorsque vous déclarez une variable en 'var'dehors de la fonction, elle devient une variable globale (vous pouvez la vérifier en tapant iou window.idans la fenêtre de la console. Elle retournera 3).
  2. La fonction anormale que vous avez déclarée n'appellera pas et ne vérifiera 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 Globalobjet et affiche le résultat.

CASE2: utiliser let

Maintenant remplacer le 'var'avec'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 développez Blockobjet, vous verrez que 'i' est défini ici, et ce qui est étrange, c'est que, pour toutes les fonctions, la valeur if iest différente (0, 1, 2).

Conclusion:

Lorsque vous déclarez une variable utilisant '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 Blockvariable de niveau uniquement disponible pour la même fonction uniquement. C'est la raison pour laquelle nous obtenons une valeur idifférente. pour chaque fonction lorsque nous appelons les fonctions.

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


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/


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

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

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

Fermetures en boucles

L'utilisation d'une fermeture dans une boucle est délicate.

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 les counters[0] et les counters[1] ne sont pas 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 les pièges et les utilisations de la fermeture JavaScript


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 sous- IIFE est d'encapsuler tout le corps de la boucle for avec une IIFE (expression de fonction immédiate), de passer new_i tant que paramètre et de la capturer sous forme de i . Comme la fonction anonyme est 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, cela est voulu, cela ne devrait pas être un problème du tout!


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 est de 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++) {
    funcs[j]();                        // and now let's run each one to see
}

Etant donné qu'il n'y a pas de portée de bloc dans JavaScript, mais uniquement la portée de fonction, 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 généralisée 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. Autrement dit, si vous avez une sorte de tableau contenant des valeurs (références DOM, objets, etc.) et que le problème de la configuration de 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 dans jQuery, la fonction $.each() vous offre une capacité similaire.

Solution ES6: let

ECMAScript 6 (ES6), la version la plus récente de JavaScript, commence à être implémentée dans de nombreux navigateurs et systèmes dorsaux persistants. Il existe également des transpileurs comme Babel qui convertiront l'ES6 en ES5 pour permettre l'utilisation de nouvelles fonctionnalités sur des systèmes plus anciens.

ES6 introduit de 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 i où chaque valeur est délimitée à l'intérieur de la boucle, afin que votre code fonctionne comme prévu. Il existe de nombreuses ressources, mais je recommanderais le poste de couverture 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 à la prise en charge de Edge 14 let mais ne donnent pas l'erreur ci-dessus (ils ne créent pas un nouveau i chaque fois, donc toutes les fonctions ci-dessus consignent 3 comme si nous utilisions var ). Edge 14 réussit enfin.


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 pour consigner 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 ferment sur la portée dans laquelle elles sont déclarées et conservent l'accès à cette portée même lorsque les valeurs de variable à l'intérieur de cette portée changent."

Utiliser 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, en 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]()
}

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


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 étendues, ce qui est essentiellement ce à quoi cela se résume.

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, chaque fonction interne produisant la dernière valeur attribuée à i , chaque fonction interne ne ilocal la dernière valeur attribuée à ilocal . Mais chaque itération ne devrait-elle pas avoir son propre ilocal ?

Il s'avère que c'est le problème. Chaque itération partage la même étendue, 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 avec 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 d'étendue. 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 font quelque chose de similaire à 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 contenant

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 délicat. 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.

Cela signifie également que les fonctions internes "s'accrochent" aux 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 dans le but de créer une nouvelle étendue, de déclarer ilocal dans la nouvelle étendue et de renvoyer 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 confère à 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 ilocal pas et ne se ilocal pas. Quelques optimisations mineures donnent la réponse finale donnée par de nombreux autres utilisateurs de SO:

//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 à présent, nous pouvons désormais utiliser le nouveau mot let 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 cette réponse , sur laquelle mes informations sont basées.


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

METTRE À JOUR

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


Utilisation d'une expression de fonction appelée immédiatement , le moyen le plus simple et le plus lisible de joindre 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, où la variable i est enregistrée pour une utilisation ultérieure dans toute fonctionnalité asynchrone au sein du IIFE.


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

CONTRE ÊTRE UN 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 écoulé, il en imprimera 2 pour les deux. En effet, la fonction de rappel accède à la valeur en fonction de la portée lexicale , où elle était définie.

Pour transmettre et conserver la valeur pendant que le rappel a été défini, nous pouvons créer une closure afin de conserver la valeur avant l'appel du rappel. 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."

CONTRE Ê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

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 funcs[]tableau est en cours d'initialisation, iest incrémenté, le funcstableau est initialisé et la taille du functableau devient 3, donc i = 3,. Maintenant, lorsque le funcs[j]()est appelé, il 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 iavec letou initialiser une nouvelle variable indexavec letet la rendre égale à i. Ainsi, lors de l'appel, indexsera utilisé et sa portée se terminera après l'initialisation. Et pour appeler, indexsera à 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. Autre option peut être d'introduire un tempFuncqui retourne 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]();                        
    }
    

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 variable ilorsque la fonction est appelée? Comme la première boucle est créée avec la condition de i < 3, elle s’arrête immédiatement lorsque la condition est fausse 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 donc quand ils sont appelés plus tard, l'interprète les exécute et demande: "Quelle est la valeur actuelle de i?"

Donc, votre objectif est d’abord de sauvegarder la valeur de ipour fonctionner et ensuite seulement d’enregistrer 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 xet nous la définirons xcomme valeur idans chaque itération.

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


Cette question montre vraiment l'histoire de JavaScript! Nous pouvons maintenant é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>


De nombreuses solutions semblent correctes, mais elles ne mentionnent pas le nom appelé Curryingmodèle de conception de programmation fonctionnelle 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 .


Je préfère utiliser la forEachfonction, 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.


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

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

// édité pour utiliser à la forEachplace de la carte.


Utilisez la structure de closure , 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);
}

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







closures