JavaScript-Schließung in Schleifen - einfaches praktisches Beispiel

2817

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

Es gibt Folgendes aus:

Mein Wert: 3
Mein Wert: 3
Mein Wert: 3

Ich möchte, dass es Folgendes ausgibt:

Mein Wert: 0
Mein Wert: 1
Mein Wert: 2


Das gleiche Problem tritt auf, wenn die Verzögerung beim Ausführen der Funktion durch die Verwendung von Ereignis-Listenern verursacht wird:

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>

… Oder asynchroner Code, zB mit Promises:

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

Bearbeiten: Es ist auch in for inund for ofSchleifen ersichtlich :

const arr = [1,2,3];
const fns = [];

for(i in arr){
  fns.push(() => console.log(`index: ${i}`));
}

for(v of arr){
  fns.push(() => console.log(`value: ${v}`));
}

for(f of fns){
  f();
}

Was ist die Lösung für dieses Grundproblem?

nickf
quelle
55
Sie möchten funcssicher kein Array sein, wenn Sie numerische Indizes verwenden? Nur ein Kopf hoch.
DanMan
23
Das ist wirklich ein verwirrendes Problem. Dieser Artikel hilft mir, es zu verstehen . Könnte es auch anderen helfen?
user3199690
4
Eine weitere einfache und erläuterte Lösung: 1) Verschachtelte Funktionen haben Zugriff auf den Bereich "über" ihnen ; 2) eine Verschlusslösung ... „Ein Verschluß ist eine Funktion Zugang zu dem übergeordneten Bereich aufweist, auch nach der übergeordneten Funktion geschlossen“.
Peter Krauss
2
Weitere Informationen finden Sie unter diesem Link. Unserstanding javascript.info/tutorial/advanced-functions
Saurabh Ahuja
35
In ES6 besteht eine triviale Lösung darin, die Variable i mit let zu deklarieren , die sich auf den Körper der Schleife bezieht.
Tomas Nikodym

Antworten:

2147

Das Problem ist, dass die Variable iinnerhalb jeder Ihrer anonymen Funktionen an dieselbe Variable außerhalb der Funktion gebunden ist.

Klassische Lösung: Verschlüsse

Sie möchten die Variable innerhalb jeder Funktion an einen separaten, unveränderlichen Wert außerhalb der Funktion binden:

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

Da es in JavaScript keinen Blockbereich gibt - nur Funktionsbereich -, indem Sie die Funktionserstellung in eine neue Funktion einschließen, stellen Sie sicher, dass der Wert von "i" wie beabsichtigt bleibt.


ES5.1-Lösung: für jeden

Angesichts der relativ weit verbreiteten Verfügbarkeit der Array.prototype.forEachFunktion (im Jahr 2015) ist anzumerken, dass in Situationen, in denen die Iteration hauptsächlich über eine Reihe von Werten erfolgt, .forEach()eine saubere, natürliche Möglichkeit besteht, für jede Iteration einen eindeutigen Abschluss zu erhalten. Angenommen, Sie haben eine Art Array, das Werte enthält (DOM-Referenzen, Objekte usw.), und das Problem beim Einrichten von Rückrufen, die für jedes Element spezifisch sind, können Sie Folgendes tun:

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

Die Idee ist, dass jeder Aufruf der Rückruffunktion, die mit der .forEachSchleife verwendet wird, ein eigener Abschluss ist. Der an diesen Handler übergebene Parameter ist das Array-Element, das für diesen bestimmten Schritt der Iteration spezifisch ist. Wenn es in einem asynchronen Rückruf verwendet wird, kollidiert es nicht mit anderen Rückrufen, die in anderen Schritten der Iteration eingerichtet wurden.

Wenn Sie zufällig in jQuery arbeiten, $.each()bietet Ihnen die Funktion eine ähnliche Funktion.


ES6-Lösung: let

ECMAScript 6 (ES6) führt neue letund constSchlüsselwörter ein, deren Gültigkeitsbereich sich von varVariablen auf Basis unterscheidet . Beispielsweise hat in einer Schleife mit einem letIndex auf Basis jeder Iteration durch die Schleife eine neue Variable imit Schleifenbereich, sodass Ihr Code wie erwartet funktioniert. Es gibt viele Ressourcen, aber ich würde den Block-Scoping-Beitrag von 2ality als hervorragende Informationsquelle empfehlen .

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

Beachten Sie jedoch, dass IE9-IE11 und Edge vor Edge 14 unterstützt werden, letaber die oben genannten Fehler auftreten (sie erstellen nicht ijedes Mal eine neue , sodass alle oben genannten Funktionen 3 protokollieren würden, wie wenn wir sie verwenden würden var). Edge 14 macht es endlich richtig.

Harto
quelle
7
ist nicht function createfunc(i) { return function() { console.log("My value: " + i); }; }immer noch Abschluss, weil es die Variable verwendet i?
レ ッ ク
55
Leider ist diese Antwort veraltet und niemand wird die richtige Antwort unten sehen - die Verwendung Function.bind()ist derzeit definitiv vorzuziehen, siehe stackoverflow.com/a/19323214/785541 .
Wladimir Palant
81
@Wladimir: Ihr Vorschlag, .bind()ist „die richtige Antwort“ ist nicht richtig. Sie haben jeweils ihren eigenen Platz. Mit können .bind()Sie keine Argumente binden, ohne den thisWert zu binden . Außerdem erhalten Sie eine Kopie des iArguments, ohne es zwischen Aufrufen mutieren zu können, was manchmal erforderlich ist. Es handelt sich also um ganz andere Konstrukte, ganz zu schweigen davon, dass die .bind()Implementierungen historisch langsam waren. Sicher, in dem einfachen Beispiel würde beides funktionieren, aber Verschlüsse sind ein wichtiges Konzept, um es zu verstehen, und darum ging es bei der Frage.
Keks Monster
8
Bitte beenden Sie die Verwendung dieser For-Return-Funktions-Hacks. Verwenden Sie stattdessen [] .forEach oder [] .map, da sie die Wiederverwendung derselben Bereichsvariablen vermeiden.
Christian Landgren
32
@ChristianLandgren: Das ist nur nützlich, wenn Sie ein Array iterieren. Diese Techniken sind keine "Hacks". Sie sind wesentliches Wissen.
379

Versuchen:

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

Bearbeiten (2014):

Persönlich denke ich, dass die jüngste Antwort von.bind @ Aust über die Verwendung der beste Weg ist, dies jetzt zu tun. Es gibt auch lo-dash / Unterstrich ist , _.partialwenn Sie nicht brauchen oder wollen Chaos mit bind‚s thisArg.

Björn
quelle
2
irgendeine Erklärung über die }(i));?
Aswzen
3
@aswzen Ich denke, es geht ials Argument indexan die Funktion.
Jet Blue
Es wird tatsächlich ein lokaler Variablenindex erstellt.
Abhishek Singh
1
Rufen Sie sofort Function Expression, auch bekannt als IIFE, auf. (i) ist das Argument für den anonymen Funktionsausdruck, der sofort aufgerufen wird und der Index wird von i gesetzt.
Eier
348

Ein anderer Weg, der noch nicht erwähnt wurde, ist die Verwendung von 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]();
}

AKTUALISIEREN

Wie von @squint und @mekdev hervorgehoben, erzielen Sie eine bessere Leistung, indem Sie die Funktion zuerst außerhalb der Schleife erstellen und dann die Ergebnisse innerhalb der Schleife binden.

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

Aust
quelle
Dies ist, was ich heutzutage auch mache, ich mag auch Lo-Dash / Unterstrich_.partial
Björn
17
.bind()wird mit ECMAScript 6-Funktionen weitgehend veraltet sein. Außerdem werden dadurch tatsächlich zwei Funktionen pro Iteration erstellt. Erst die anonyme, dann die von .bind(). Besser wäre es, es außerhalb der Schleife zu erstellen, dann .bind()innerhalb.
5
@squint @mekdev - Sie sind beide richtig. Mein erstes Beispiel wurde schnell geschrieben, um zu demonstrieren, wie bindes verwendet wird. Ich habe gemäß Ihren Vorschlägen ein weiteres Beispiel hinzugefügt.
Aust
5
Ich denke, anstatt die Berechnung über zwei O (n) -Schleifen zu verschwenden, tun Sie einfach für (var i = 0; i <3; i ++) {log.call (this, i); }
user2290820
1
.bind () macht das, was die akzeptierte Antwort nahelegt this.
niry
269

Verwenden eines sofort aufgerufenen Funktionsausdrucks , der einfachste und am besten lesbare Weg, eine Indexvariable einzuschließen:

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

}

Dies sendet den Iterator iin die anonyme Funktion, als die wir definieren index. Dadurch wird ein Abschluss erstellt, in dem die Variable izur späteren Verwendung in einer asynchronen Funktionalität innerhalb des IIFE gespeichert wird.

Neurosnap
quelle
10
Um die Lesbarkeit des Codes zu verbessern und Unklarheiten darüber zu vermeiden, welche iwas ist, würde ich den Funktionsparameter in umbenennen index.
Kyle Falconer
5
Wie würden Sie diese Technik verwenden, um die in der ursprünglichen Frage beschriebenen Array- Funktionen zu definieren ?
Nico
@Nico Der gleiche Weg wie in der ursprünglichen Frage gezeigt, außer dass Sie indexanstelle von verwenden würden i.
JLRishe
@ JLRishevar funcs = {}; for (var i = 0; i < 3; i++) { funcs[i] = (function(index) { return function() {console.log('iterator: ' + index);}; })(i); }; for (var j = 0; j < 3; j++) { funcs[j](); }
Nico
1
@Nico In OPs speziellem Fall iterieren sie nur über Zahlen, daher wäre dies kein guter Fall, aber die meiste .forEach()Zeit, wenn man mit einem Array beginnt, forEach()ist eine gute Wahl, wie:var nums [4, 6, 7]; var funcs = {}; nums.forEach(function (num, i) { funcs[i] = function () { console.log(num); }; });
JLRishe
164

Etwas spät zur Party, aber ich habe dieses Problem heute untersucht und festgestellt, dass viele der Antworten nicht vollständig darauf eingehen, wie Javascript Bereiche behandelt, worauf es im Wesentlichen ankommt.

Wie viele andere erwähnt haben, besteht das Problem darin, dass die innere Funktion auf dieselbe iVariable verweist . Warum erstellen wir nicht einfach bei jeder Iteration eine neue lokale Variable und haben stattdessen die innere Funktionsreferenz darauf?

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

Genau wie zuvor, wo jede innere Funktion den zuletzt zugewiesenen Wert ausgegeben hat i, gibt jetzt jede innere Funktion nur den zuletzt zugewiesenen Wert aus ilocal. Aber sollte nicht jede Iteration ihre eigene haben ilocal?

Es stellt sich heraus, das ist das Problem. Jede Iteration hat denselben Gültigkeitsbereich, sodass jede Iteration nach der ersten nur überschrieben wird ilocal. Von MDN :

Wichtig: JavaScript hat keinen Blockbereich. Mit einem Block eingeführte Variablen sind auf die enthaltene Funktion oder das Skript beschränkt, und die Auswirkungen ihrer Einstellung bleiben über den Block selbst hinaus bestehen. Mit anderen Worten, Blockanweisungen führen keinen Bereich ein. Obwohl "eigenständige" Blöcke eine gültige Syntax sind, möchten Sie keine eigenständigen Blöcke in JavaScript verwenden, da sie nicht das tun, was Sie denken, wenn Sie glauben, dass sie solche Blöcke in C oder Java ausführen.

Zur Betonung wiederholt:

JavaScript hat keinen Blockbereich. Mit einem Block eingeführte Variablen sind auf die enthaltende Funktion oder das Skript beschränkt

Wir können dies sehen, indem ilocalwir überprüfen, bevor wir es in jeder Iteration deklarieren:

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

Genau deshalb ist dieser Fehler so schwierig. Obwohl Sie eine Variable neu deklarieren, gibt Javascript keinen Fehler aus und JSLint gibt nicht einmal eine Warnung aus. Dies ist auch der Grund, warum der beste Weg, dies zu lösen, darin besteht, Verschlüsse zu nutzen. Dies ist im Wesentlichen die Idee, dass innere Funktionen in Javascript Zugriff auf äußere Variablen haben, da innere Bereiche äußere Bereiche "einschließen".

Verschlüsse

Dies bedeutet auch, dass innere Funktionen äußere Variablen "festhalten" und am Leben erhalten, selbst wenn die äußere Funktion zurückkehrt. Um dies zu nutzen, erstellen und rufen wir eine Wrapper-Funktion auf, um lediglich einen neuen Bereich zu erstellen, den neuen Bereich zu deklarieren ilocalund eine innere Funktion zurückzugeben, die verwendet ilocal(weitere Erläuterungen unten):

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

Durch das Erstellen der inneren Funktion innerhalb einer Wrapper-Funktion erhält die innere Funktion eine private Umgebung, auf die nur sie zugreifen kann, einen "Abschluss". Daher erstellen wir jedes Mal, wenn wir die Wrapper-Funktion aufrufen, eine neue innere Funktion mit einer eigenen separaten Umgebung, um sicherzustellen, dass die ilocalVariablen nicht kollidieren und sich gegenseitig überschreiben. Ein paar kleinere Optimierungen geben die endgültige Antwort, die viele andere SO-Benutzer gegeben haben:

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

Aktualisieren

Mit ES6 als Mainstream können wir jetzt das neue letSchlüsselwort verwenden, um Variablen mit Blockbereich zu erstellen:

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

Schau wie einfach es jetzt ist! Weitere Informationen finden Sie in dieser Antwort , auf der meine Informationen basieren.

woojoo666
quelle
Mir gefällt auch, wie Sie den IIFE-Weg erklärt haben. Ich habe danach gesucht. Vielen Dank.
CapturedTree
4
In JavaScript gibt es jetzt so etwas wie Block Scoping mit den Schlüsselwörtern letund const. Wenn diese Antwort um diese erweitert würde, wäre sie meiner Meinung nach global nützlicher.
@TinyGiant sicher, ich habe einige Informationen hinzugefügt letund eine vollständigere Erklärung
verlinkt
@ woojoo666 Könnte Ihre Antwort auch dazu dienen, zwei abwechselnde URLs in einer Schleife wie folgt aufzurufen : i=0; while(i < 100) { setTimeout(function(){ window.open("https://www.bbc.com","_self") }, 3000); setTimeout(function(){ window.open("https://www.cnn.com","_self") }, 3000); i++ }? (könnte window.open () durch getelementbyid ersetzen ......)
verrückt nach natty
@nuttyaboutnatty Entschuldigung für eine so späte Antwort. Es scheint nicht, dass der Code in Ihrem Beispiel bereits funktioniert. Sie verwenden nicht iin Ihren Timeout-Funktionen, so dass Sie keinen Abschluss benötigen
woojoo666
151

Da ES6 jetzt weitgehend unterstützt wird, hat sich die beste Antwort auf diese Frage geändert. ES6 bietet die Schlüsselwörter letund constfür diesen genauen Umstand. Anstatt mit Abschlüssen herumzuspielen, können wir einfach leteine Schleifenbereichsvariable wie folgt festlegen:

var funcs = [];

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

valzeigt dann auf ein Objekt, das für diese bestimmte Umdrehung der Schleife spezifisch ist, und gibt den korrekten Wert ohne die zusätzliche Abschlussnotation zurück. Dies vereinfacht dieses Problem offensichtlich erheblich.

constähnelt letder zusätzlichen Einschränkung, dass der Variablenname nach der ersten Zuweisung nicht auf eine neue Referenz zurückgesetzt werden kann.

Die Browserunterstützung ist jetzt für diejenigen verfügbar, die auf die neuesten Browserversionen abzielen. const/ letwerden derzeit in den neuesten Versionen von Firefox, Safari, Edge und Chrome unterstützt. Es wird auch in Node unterstützt und Sie können es überall verwenden, indem Sie Build-Tools wie Babel nutzen. Ein funktionierendes Beispiel finden Sie hier: http://jsfiddle.net/ben336/rbU4t/2/

Docs hier:

Beachten Sie jedoch, dass IE9-IE11 und Edge vor Edge 14 unterstützt werden, letaber die oben genannten Fehler auftreten (sie erstellen nicht ijedes Mal eine neue , sodass alle oben genannten Funktionen 3 protokollieren würden, wie wenn wir sie verwenden würden var). Edge 14 macht es endlich richtig.

Ben McCormick
quelle
Leider wird 'let' immer noch nicht vollständig unterstützt, insbesondere bei Mobilgeräten. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
MattC
2
Ab Juni '16 wird let in allen gängigen Browserversionen mit Ausnahme von iOS Safari, Opera Mini und Safari 9 unterstützt. Evergreen-Browser unterstützen dies. Babel wird es korrekt transpilieren, um das erwartete Verhalten ohne eingeschalteten Modus für hohe Konformität beizubehalten.
Dan Pantry
@DanPantry Ja, es ist Zeit für ein Update :) Aktualisiert, um den aktuellen Stand der Dinge besser widerzuspiegeln, einschließlich der Erwähnung von const, doc-Links und besseren Kompatibilitätsinformationen.
Ben McCormick
Verwenden wir deshalb nicht babel, um unseren Code zu transpilieren, damit Browser, die ES6 / 7 nicht unterstützen, verstehen können, was los ist?
Pixel 67
87

Eine andere Art zu sagen ist, dass die iin Ihrer Funktion zum Zeitpunkt der Ausführung der Funktion gebunden ist, nicht zum Zeitpunkt der Erstellung der Funktion.

Wenn Sie den Abschluss erstellen, iist dies ein Verweis auf die im externen Bereich definierte Variable und keine Kopie davon, wie sie beim Erstellen des Abschlusses war . Es wird zum Zeitpunkt der Ausführung ausgewertet.

Die meisten anderen Antworten bieten Möglichkeiten zur Umgehung, indem Sie eine weitere Variable erstellen, die den Wert für Sie nicht ändert.

Ich dachte nur, ich würde eine Erklärung für Klarheit hinzufügen. Für eine Lösung persönlich würde ich mich für Harto entscheiden, da dies die selbsterklärendste Methode ist, dies anhand der Antworten hier zu tun. Jeder der veröffentlichten Codes wird funktionieren, aber ich würde mich für eine Closure Factory entscheiden, weil ich einen Stapel Kommentare schreiben muss, um zu erklären, warum ich eine neue Variable deklariere (Freddy und 1800) oder eine seltsame eingebettete Closure-Syntax (Apphacker) habe.

Darren Clark
quelle
71

Was Sie verstehen müssen, ist, dass der Umfang der Variablen in Javascript auf der Funktion basiert. Dies ist ein wichtiger Unterschied zu c #, wenn Sie einen Blockbereich haben, und das Kopieren der Variablen in eine Variable innerhalb des for funktioniert.

Das Einschließen in eine Funktion, die die Rückgabe der Funktion wie die Antwort des Apphackers auswertet, reicht aus, da die Variable jetzt den Funktionsumfang hat.

Es gibt auch ein let-Schlüsselwort anstelle von var, mit dem die Blockbereichsregel verwendet werden kann. In diesem Fall würde das Definieren einer Variablen innerhalb des for den Trick tun. Das Schlüsselwort let ist jedoch aus Kompatibilitätsgründen keine praktische Lösung.

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

eglasius
quelle
@nickf welcher Browser? Wie gesagt, es gibt Kompatibilitätsprobleme. Damit meine ich ernsthafte Kompatibilitätsprobleme, da ich nicht glaube, dass let im IE unterstützt wird.
Eglasius
1
@nickf ja, überprüfen Sie diese Referenz: developer.mozilla.org/En/New_in_JavaScript_1.7 ... überprüfen Sie den Abschnitt let Definitionen, es gibt ein Onclick-Beispiel in einer Schleife
eglasius
2
@nickf hmm, eigentlich musst du die Version explizit angeben: <script type = "application / javascript; version = 1.7" /> ... Ich habe es wegen der IE-Einschränkung nirgendwo benutzt, es ist einfach nicht praktisch :(
eglasius
Sie können Browser-Unterstützung für die verschiedenen Versionen hier sehen es.wikipedia.org/wiki/Javascript
eglasius
59

Hier ist eine weitere Variation der Technik, ähnlich der von Björn (Apphacker), mit der Sie den Variablenwert innerhalb der Funktion zuweisen können, anstatt ihn als Parameter zu übergeben, was manchmal klarer sein kann:

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

Beachten Sie, dass die indexVariable unabhängig von der verwendeten Technik zu einer Art statischer Variable wird, die an die zurückgegebene Kopie der inneren Funktion gebunden ist. Das heißt, Änderungen an seinem Wert bleiben zwischen den Aufrufen erhalten. Es kann sehr praktisch sein.

Boann
quelle
Danke und Ihre Lösung funktioniert. Aber ich würde gerne fragen, warum das funktioniert, aber das Vertauschen der varLeitung und der returnLeitung würde nicht funktionieren? Vielen Dank!
Midnite
@midnite Wenn Sie getauscht haben varund returndie Variable dann nicht zugewiesen wurde, bevor sie die innere Funktion zurückgegeben hat.
Boann
53

Dies beschreibt den häufigen Fehler bei der Verwendung von Verschlüssen in JavaScript.

Eine Funktion definiert eine neue Umgebung

Erwägen:

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

Bei jedem makeCounterAufruf wird {counter: 0}ein neues Objekt erstellt. Außerdem wird eine neue Kopie von obj erstellt, um auf das neue Objekt zu verweisen. Somit counter1und counter2sind unabhängig voneinander.

Verschlüsse in Schleifen

Die Verwendung eines Verschlusses in einer Schleife ist schwierig.

Erwägen:

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

Beachten Sie, dass counters[0]und counters[1]sind nicht unabhängig. In der Tat arbeiten sie auf dem gleichenobj !

Dies liegt daran, dass es nur eine Kopie von gibt obj allen Iterationen der Schleife gemeinsam genutzt wird, möglicherweise aus Leistungsgründen. Obwohl {counter: 0}in jeder Iteration ein neues Objekt erstellt wird, wird dieselbe Kopie von objnur mit einem Verweis auf das neueste Objekt aktualisiert.

Die Lösung besteht darin, eine andere Hilfsfunktion zu verwenden:

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

Dies funktioniert, weil lokalen Variablen im Funktionsumfang direkt sowie Funktionsargumentvariablen bei der Eingabe neue Kopien zugewiesen werden.


quelle
Kleine Klarstellung: Im ersten Beispiel für Closures in Schleifen sind Zähler [0] und Zähler [1] nicht aus Leistungsgründen unabhängig. Der Grund dafür ist, dass var obj = {counter: 0};vor der Ausführung eines Codes Folgendes ausgewertet wird: MDN var : var-Deklarationen werden, wo immer sie auftreten, verarbeitet, bevor ein Code ausgeführt wird.
Charidimos
50

Die einfachste Lösung wäre:

Anstatt zu verwenden:

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

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

was dreimal "2" warnt. Dies liegt daran, dass anonyme Funktionen, die in der for-Schleife erstellt wurden, denselben Abschluss haben und in diesem Abschluss der Wert von igleich ist. Verwenden Sie dies, um ein gemeinsames Schließen zu verhindern:

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

Die Idee dahinter ist, den gesamten Körper der for-Schleife mit einem IIFE (Sofort aufgerufener Funktionsausdruck) zu kapseln , new_ials Parameter zu übergeben und als zu erfassen i. Da die anonyme Funktion sofort ausgeführt wird, wird diei Wert für jede in der anonymen Funktion definierte Funktion unterschiedlich.

Diese Lösung scheint zu jedem solchen Problem zu passen, da nur minimale Änderungen am ursprünglichen Code erforderlich sind, der unter diesem Problem leidet. In der Tat ist dies beabsichtigt, es sollte überhaupt kein Problem sein!

Kemal Dağ
quelle
2
Lesen Sie einmal etwas Ähnliches in einem Buch. Ich bevorzuge dies auch, da Sie Ihren vorhandenen Code nicht (so oft) berühren müssen und es offensichtlich wird, warum Sie es getan haben, sobald Sie das selbstaufrufende Funktionsmuster gelernt haben: diese Variable in der neu erstellten einzufangen Umfang.
DanMan
1
@ DanMan Danke. Selbstaufrufende anonyme Funktionen sind ein sehr guter Weg, um den Mangel an Variablenbereich auf Blockebene in Javascript zu beheben.
Kemal Dağ
3
Selbstaufruf oder Selbstaufruf ist nicht der geeignete Begriff für diese Technik. IIFE (Sofort aufgerufene Funktionsausdrücke) ist genauer. Ref: benalman.com/news/2010/11/…
jherax
31

versuchen Sie dieses kürzere

  • kein Array

  • kein extra für loop


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

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

http://jsfiddle.net/7P6EN/

Yilmazburk
quelle
1
Ihre Lösung scheint korrekt auszugeben, verwendet jedoch unnötigerweise Funktionen. Warum nicht einfach console.log die Ausgabe? Die ursprüngliche Frage betrifft die Erstellung anonymer Funktionen mit demselben Abschluss. Das Problem war, da sie einen einzigen Abschluss haben, ist der Wert von i für jeden von ihnen gleich. Ich hoffe du hast es verstanden.
Kemal Dağ
30

Hier ist eine einfache Lösung, die verwendet forEach(funktioniert zurück zu 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
}

Drucke:

My value: 0
My value: 1
My value: 2
Daryl
quelle
27

Das Hauptproblem mit dem vom OP angezeigten Code ist, dass er ierst in der zweiten Schleife gelesen wird. Stellen Sie sich zur Demonstration einen Fehler im Code vor

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

Der Fehler tritt tatsächlich erst auf, wenn er funcs[someIndex]ausgeführt wird (). Unter Verwendung derselben Logik sollte es offensichtlich sein, dass der Wert von iauch erst zu diesem Zeitpunkt erfasst wird. Sobald die ursprünglichen Schleife beendet ist , i++bringt iauf den Wert von 3dem in dem Zustand führti < 3 versagen und die Endung Schleife. An diesem Punkt iist 3und so, wann funcs[someIndex]()verwendet wird, undi ausgewertet wird, ist es 3 - jedes Mal.

Um dies zu überwinden, müssen Sie bewerten, iwie es angetroffen wird. Beachten Sie, dass dies bereits in Form von funcs[i](wo es 3 eindeutige Indizes gibt) geschehen ist . Es gibt verschiedene Möglichkeiten, diesen Wert zu erfassen. Eine besteht darin, es als Parameter an eine Funktion zu übergeben, die hier bereits auf verschiedene Arten gezeigt wird.

Eine andere Möglichkeit besteht darin, ein Funktionsobjekt zu erstellen, das über der Variablen geschlossen werden kann. Das kann so erreicht werden

jsFiddle Demo

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

JavaScript-Funktionen "schließen" den Bereich, auf den sie bei der Deklaration Zugriff haben, und behalten den Zugriff auf diesen Bereich bei, selbst wenn sich Variablen in diesem Bereich ändern.

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

Jede Funktion im obigen Array wird über den globalen Bereich geschlossen (global, einfach weil dies der Bereich ist, in dem sie deklariert sind).

Später werden diese Funktionen aufgerufen, um den aktuellsten Wert iim globalen Bereich zu protokollieren . Das ist die Magie und Frustration der Schließung.

"JavaScript-Funktionen schließen über den Bereich, in dem sie deklariert sind, und behalten den Zugriff auf diesen Bereich, auch wenn sich die Variablenwerte innerhalb dieses Bereichs ändern."

Verwenden Sie letstatt zu varlösen, indem Sie bei jeder Ausführung der forSchleife einen neuen Bereich erstellen und einen separaten Bereich für jede Funktion erstellen, die geschlossen werden soll. Verschiedene andere Techniken machen dasselbe mit zusätzlichen Funktionen.

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

( letmacht Variablen blockumfangig. Blöcke werden durch geschweifte Klammern gekennzeichnet, aber im Fall der for-Schleife wird die Initialisierungsvariable iin unserem Fall als in den Klammern deklariert betrachtet.)

Costa
quelle
1
Ich hatte Mühe, dieses Konzept zu verstehen, bis ich diese Antwort gelesen hatte. Es berührt einen wirklich wichtigen Punkt - der Wert von iwird auf den globalen Bereich gesetzt. Wenn die forSchleife beendet ist, ist der globale Wert von ijetzt 3. Wenn diese Funktion im Array aufgerufen wird (z. B. mit funcs[j]), iverweist die Funktion in dieser Funktion auf die globale iVariable (3).
Modermo
13

Nachdem ich verschiedene Lösungen durchgelesen habe, möchte ich hinzufügen, dass der Grund, warum diese Lösungen funktionieren, darin besteht, sich auf das Konzept der Scope-Kette zu stützen . Auf diese Weise löst JavaScript eine Variable während der Ausführung auf.

  • Jede Funktionsdefinition bildet einen Bereich, der aus allen von varund seinen deklarierten lokalen Variablen besteht arguments.
  • Wenn wir eine innere Funktion in einer anderen (äußeren) Funktion definiert haben, bildet diese eine Kette und wird während der Ausführung verwendet
  • Wenn eine Funktion ausgeführt wird, wertet die Laufzeit Variablen aus, indem sie die Bereichskette durchsucht . Wenn eine Variable an einem bestimmten Punkt der Kette gefunden werden kann, wird die Suche beendet und verwendet. Andernfalls wird sie fortgesetzt, bis der globale Bereich erreicht ist, zu dem sie gehört window.

Im ursprünglichen Code:

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

Wenn funcsausgeführt wird, wird die Scope-Kette sein function inner -> global. Da die Variable iin nicht gefunden werden kann function inner(weder mit varals deklariert noch als Argumente übergeben), wird die Suche fortgesetzt, bis der Wert von ischließlich im globalen Bereich gefunden wird window.i.

Indem Sie es in eine äußere Funktion einschließen, definieren Sie entweder explizit eine Hilfsfunktion wie Harto oder verwenden Sie eine anonyme Funktion wie Björn :

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

Wenn funcsausgeführt wird, ist jetzt die Scope-Kette function inner -> function outer. Diese Zeit ibefindet sich im Bereich der äußeren Funktion, der dreimal in der for-Schleife ausgeführt wird, wobei jedes Mal der Wert ikorrekt gebunden ist. Es wird nicht der Wert verwendet, window.iwenn inner ausgeführt wird.

Weitere Details finden Sie hier.
Es enthält den häufigen Fehler beim Erstellen eines Abschlusses in der Schleife als das, was wir hier haben, sowie warum wir einen Abschluss benötigen und die Leistungsüberlegung.

wpding
quelle
Wir schreiben dieses Codebeispiel selten in real, aber ich denke, es ist ein gutes Beispiel, um die Grundlagen zu verstehen. Sobald wir den Umfang im Auge haben und wie sie miteinander verkettet sind, wird klarer, warum andere "moderne" Methoden wie Array.prototype.forEach(function callback(el) {})natürlich funktionieren: Der Rückruf, der auf natürliche Weise übergeben wird, bildet den Umbruchbereich, wobei el in jeder Iteration von korrekt gebunden ist forEach. So kann jede im Rückruf definierte innere Funktion den richtigen elWert verwenden
wpding
13

Mit den neuen Funktionen von ES6 wird das Scoping auf Blockebene verwaltet:

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
}

Der Code in der Frage von OP wird durch letanstelle von ersetzt var.

Prithvi Uppalapati
quelle
constliefert das gleiche Ergebnis und sollte verwendet werden, wenn sich der Wert einer Variablen nicht ändert. Die Verwendung constinnerhalb des Initialisierers der for-Schleife ist in Firefox jedoch falsch implementiert und muss noch behoben werden. Anstatt innerhalb des Blocks deklariert zu werden, wird es außerhalb des Blocks deklariert, was zu einer erneuten Deklaration der Variablen führt, was wiederum zu einem Fehler führt. Die Verwendung des letInitialisierers ist in Firefox korrekt implementiert, sodass Sie sich dort keine Sorgen machen müssen.
10

Ich bin überrascht, dass noch niemand vorgeschlagen hat, die forEachFunktion zu verwenden, um die (lokale) Verwendung lokaler Variablen besser zu vermeiden. Tatsächlich benutze ich for(var i ...)aus diesem Grund überhaupt nicht mehr.

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

// bearbeitet, um forEachanstelle der Karte zu verwenden.

Christian Landgren
quelle
3
.forEach()ist eine viel bessere Option, wenn Sie eigentlich nichts zuordnen, und Daryl schlug dies 7 Monate vor dem Posten vor, sodass Sie sich nicht wundern sollten.
JLRishe
Diese Frage ist nicht über Schleife über ein Array
Jherax
Nun, er möchte ein Array von Funktionen erstellen. Dieses Beispiel zeigt, wie das geht, ohne eine globale Variable einzubeziehen.
Christian Landgren
9

Diese Frage zeigt wirklich die Geschichte von JavaScript! Jetzt können wir das Block-Scoping mit Pfeilfunktionen vermeiden und Schleifen direkt von DOM-Knoten mithilfe von Object-Methoden verarbeiten.

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>

Sidhuko
quelle
8

Der Grund, warum Ihr ursprüngliches Beispiel nicht funktioniert hat, ist, dass alle in der Schleife erstellten Abschlüsse auf denselben Frame verweisen. In der Tat 3 Methoden für ein Objekt mit nur einer einzigen iVariablen. Sie alle druckten den gleichen Wert aus.

Jottos
quelle
8

Verstehen Sie zunächst, was mit diesem Code nicht stimmt:

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
}

Hier wird, wenn das funcs[]Array initialisiert iwird, inkrementiert, das funcsArray initialisiert und die Größe des funcArrays wird 3, also i = 3,. Wenn das funcs[j]()aufgerufen wird, verwendet es wieder die Variablei , die bereits auf 3 erhöht wurde.

Um dies zu lösen, haben wir viele Möglichkeiten. Unten sind zwei davon:

  1. Wir können initialisieren imit letoder eine neue Variable initialisieren indexmit letund machen es gleich zu i. Wenn der Anruf getätigt wird, indexwird er verwendet und sein Gültigkeitsbereich endet nach der Initialisierung. Und für den Anruf indexwird erneut initialisiert:

    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. Eine andere Option kann darin bestehen, eine einzuführen, tempFuncdie die eigentliche Funktion zurückgibt:

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

Verwenden Sie eine Verschlussstruktur , dies würde Ihre zusätzliche for-Schleife reduzieren. Sie können dies in einer einzigen for-Schleife tun:

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

Wir werden prüfen, was tatsächlich passiert , wenn Sie erklären , varund let eins nach dem anderen.

Fall 1 : Verwendenvar

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

Öffnen Sie nun Ihr Chrome-Konsolenfenster, indem Sie F12 drücken und die Seite aktualisieren. Erweitern Sie alle 3 Funktionen innerhalb des Arrays. Sie sehen eine Eigenschaft namens [[Scopes]].Expand diese. Sie sehen ein Array-Objekt namens "Global", erweitern Sie dieses. Sie finden eine im Objekt 'i'deklarierte Eigenschaft mit dem Wert 3.

Geben Sie hier die Bildbeschreibung ein

Geben Sie hier die Bildbeschreibung ein

Fazit:

  1. Wenn Sie eine Variable 'var'außerhalb einer Funktion deklarieren , wird sie zu einer globalen Variablen (Sie können dies durch Eingabe ioder window.iim Konsolenfenster überprüfen. Es wird 3 zurückgegeben).
  2. Die von Ihnen deklarierte annominöse Funktion ruft den Wert innerhalb der Funktion nur auf und überprüft ihn, wenn Sie die Funktionen aufrufen.
  3. Wenn Sie die Funktion aufrufen, console.log("My value: " + i)nehmen Sie den Wert aus ihrem GlobalObjekt und zeigen Sie das Ergebnis an.

FALL2: mit let

Ersetzen Sie nun die 'var'durch'let'

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

Mach dasselbe, geh in die Zielfernrohre. Jetzt sehen Sie zwei Objekte "Block"und "Global". Erweitern BlockSie nun das Objekt. Sie werden sehen, dass dort 'i' definiert ist, und das Seltsame ist, dass für jede Funktion der Wert if iunterschiedlich ist (0, 1, 2).

Geben Sie hier die Bildbeschreibung ein

Fazit:

Wenn Sie eine Variable 'let'auch außerhalb der Funktion, aber innerhalb der Schleife deklarieren , ist diese Variable keine globale Variable, sondern eine BlockEbenenvariable, die nur für dieselbe Funktion verfügbar ist. Aus diesem Grund erhalten wir den Wert idifferent für jede Funktion, wenn wir die Funktionen aufrufen.

Weitere Informationen darüber, wie näher funktioniert, finden Sie im fantastischen Video-Tutorial https://youtu.be/71AtaJpJHw0

Bimal Das
quelle
4

Sie können ein deklaratives Modul für Datenlisten wie query-js (*) verwenden. In diesen Situationen finde ich persönlich einen deklarativen Ansatz weniger überraschend

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

Sie könnten dann Ihre zweite Schleife verwenden und das erwartete Ergebnis erhalten, oder Sie könnten es tun

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

(*) Ich bin der Autor von query-js und daher voreingenommen, es zu verwenden. Nehmen Sie meine Worte also nicht als Empfehlung für diese Bibliothek, nur für den deklarativen Ansatz :)

Rune FS
quelle
1
Ich würde eine Erklärung der Abwahl lieben. Der Code löst das vorliegende Problem. Es wäre wertvoll zu wissen, wie der Code möglicherweise verbessert werden kann
Rune FS
1
Was ist Query.range(0,3)? Dies ist nicht Teil der Tags für diese Frage. Wenn Sie eine Bibliothek eines Drittanbieters verwenden, können Sie außerdem den Link zur Dokumentation bereitstellen.
Jherax
1
@jherax das sind oder natürlich offensichtliche Verbesserungen. Danke für den Kommentar. Ich hätte schwören können, dass es bereits einen Link gab. Ohne dass der Beitrag ziemlich sinnlos war, denke ich :). Meine ursprüngliche Idee, es draußen zu halten, war, weil ich nicht versuchte, die Nutzung meiner eigenen Bibliothek voranzutreiben, sondern eher die deklarative Idee. Im Nachhinein stimme ich jedoch voll und ganz zu, dass der Link vorhanden sein sollte
Rune FS
4

Ich bevorzuge die Verwendung einer forEachFunktion, die einen eigenen Abschluss beim Erstellen eines Pseudobereichs hat:

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
}

Das sieht hässlicher aus als Bereiche in anderen Sprachen, aber meiner Meinung nach weniger monströs als andere Lösungen.

Rax Wunter
quelle
Lieber was? Dies scheint ein Kommentar als Antwort auf eine andere Antwort zu sein. Die eigentliche Frage wird überhaupt nicht behandelt (da Sie keine Funktion zuweisen, die später irgendwo aufgerufen werden soll).
Quentin
Es hängt genau mit dem erwähnten Problem zusammen: wie man sicher ohne
Schließprobleme
Jetzt scheint es sich nicht wesentlich von der akzeptierten Antwort zu unterscheiden.
Quentin
Nein. In der akzeptierten Antwort wird empfohlen, "irgendein Array" zu verwenden, aber wir beschäftigen uns mit einem Bereich in der Antwort. Es sind absolut verschiedene Dinge, die leider keine gute Lösung in js haben, also versucht meine Antwort zu lösen das Problem in einer guten und praktischen Weise
Rax Wunter
@ Quentin Ich würde empfehlen, die Lösung vor dem Minus zu untersuchen
Rax Wunter
4

Und noch eine andere Lösung: Anstatt eine weitere Schleife zu erstellen, binden Sie die einfach thisan die Rückgabefunktion.

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
}

Durch die Bindung dieser , löst das Problem auch.

Pixel 67
quelle
3

Viele Lösungen scheinen korrekt zu sein, erwähnen jedoch nicht, dass es sich Curryingum ein funktionales Programmierentwurfsmuster für Situationen wie hier handelt. Je nach Browser 3-10 mal schneller als Binden.

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

Sehen Sie den Leistungszuwachs in verschiedenen Browsern .

Pawel
quelle
@TinyGiant Das Beispiel mit der zurückgegebenen Funktion ist immer noch für die Leistung optimiert. Ich würde nicht wie alle JavaScript-Blogger auf Pfeilfunktionen springen. Sie sehen cool und sauber aus, fördern jedoch Inline-Schreibfunktionen, anstatt vordefinierte Funktionen zu verwenden. Dies kann an heißen Orten eine nicht offensichtliche Falle sein. Ein weiteres Problem besteht darin, dass es sich nicht nur um syntaktischen Zucker handelt, sondern dass unnötige Bindungen ausgeführt werden, wodurch Umhüllungsverschlüsse erstellt werden.
Pawel
2
Warnung an zukünftige Leser: Diese Antwort verwendet den Begriff Currying falsch . "Currying ist, wenn Sie eine Funktion, die mehrere Argumente enthält, in eine Reihe von Funktionen aufteilen, die Teil der Argumente sind." . Dieser Code macht nichts dergleichen. Alles, was Sie hier getan haben, ist, den Code aus der akzeptierten Antwort zu übernehmen, einige Dinge zu verschieben, den Stil und die Benennung ein wenig zu ändern und ihn dann Currying zu nennen, was kategorisch nicht der Fall ist.
3

Ihr Code funktioniert nicht, weil er Folgendes tut:

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?

Die Frage ist nun, was ist der Wert der Variablen, iwenn die Funktion aufgerufen wird? Da die erste Schleife mit der Bedingung von erstellt wird i < 3, stoppt sie sofort, wenn die Bedingung falsch ist i = 3.

Sie müssen verstehen, dass zum Zeitpunkt der Erstellung Ihrer Funktionen kein Code ausgeführt wird, sondern nur für später gespeichert wird. Wenn sie später aufgerufen werden, führt der Dolmetscher sie aus und fragt: "Was ist der aktuelle Wert von i?"

Ihr Ziel ist es also, zuerst den Wert von ito function und erst danach die Funktion to zu speichern funcs. Dies könnte zum Beispiel folgendermaßen geschehen:

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
}

Auf diese Weise hat jede Funktion ihre eigene Variable xund wir setzen diese in jeder Iteration xauf den Wert von i.

Dies ist nur eine von mehreren Möglichkeiten, um dieses Problem zu lösen.

Buksy
quelle
3
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
}
Ashish Yadav
quelle
3

Verwenden Sie let (Blocked-Scope) anstelle von 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]();                      
}

BG Hari Prasad
quelle