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?
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.
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.
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.
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.
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
.
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]();
}
_
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.
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.
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.
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.
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).
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]();
}
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.
Ceci décrit l’erreur commune lors de l’utilisation de fermetures en JavaScript.
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.
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
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!
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);};
}
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
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
_funcs[i] = new function() {
var closedVariable = i;
return function(){
console.log("My value: " + closedVariable);
};
};
_
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.)
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.
var
et ses arguments
.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.
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
.
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.
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>
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:
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]();
}
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]();
}
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.
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);
}
Nous vérifierons ce qui se passe réellement lorsque vous déclarez
var
etlet
un par un.
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.
Conclusion:
'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).console.log("My value: " + i)
prend la valeur de son objet Global
et affiche le résultat.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).
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
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.
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.
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 :)
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.
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
}
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);
}
}
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]();
}
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. ).
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
}
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
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!
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
}
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);
};
}
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]();
}
_
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.
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.
asyncIterable = [1,2,3,4,5,6,7,8];
(async function() {
for await (let num of asyncIterable) {
console.log(num);
}
})();
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.