2017-02-07 6 views
0

Ich habe ein Problem mit einer For-Schleife, die eine setTimeout ausgeführt.Javascript for-Schleife und Timeout-Funktion

for (var x = 0; x < 5; x++) { 
 
    var timeoutFunction = function() { 
 
     return function() { 
 
      console.log(x) 
 
     } 
 
    } 
 
    setTimeout(timeoutFunction(), 1) 
 
}

I erwarten, dass das Ausgangs

Doch aus irgendeinem Grund sie alle Ausgaben 5.

Variable x wird im lokalen Bereich der for-Schleife definiert, so dass ich dachte, das nicht für den Rückruf von setTimeout zählen kann. Ich habe mit der Definition x außerhalb der for-Schleife getestet.

var x = 10 
 
for (var x = 0; x < 5; x++) { 
 
    var timeoutFunction = function() { 
 
     return function() { 
 
      console.log(x) 
 
     } 
 
    } 
 
    setTimeout(timeoutFunction(), 1) 
 
}

dachte ich, dieser Ausgang 10 werden geben würde, aber es kam nicht. Dann dachte ich, es wäre sinnvoll, danach x zu definieren.

for (var x = 0; x < 5; x++) { 
 
    var timeoutFunction = function() { 
 
     return function() { 
 
      console.log(x) 
 
     } 
 
    } 
 
    setTimeout(timeoutFunction(), 1) 
 
} 
 
var x = 10

Diese Rückkehr ist nur 10. Das bedeutet, die Rückrufe sind alle aufgerufen, nachdem die for-Schleife ausgeführt wird? Und warum passen sie nur dann zum übergeordneten Bereich der for-Schleife, wenn die Variable nach der Ausführung der for-Schleife initialisiert wurde? Fehle ich etwas?

Ich weiß, wie dieses Beispiel mit

for (var x = 0; x < 5; x++) { 
 
    var timeoutFunction = function(x) { 
 
     return function() { 
 
      console.log(x) 
 
     } 
 
    } 
 
    setTimeout(timeoutFunction(x), 1) 
 
}

Dennoch arbeiten machen, frage ich mich wirklich, was fehlt ...

Antwort

1

Beachten Sie, dass die Angabe 1 als Verzögerungswert tatsächlich nicht dazu führt, dass die Funktion ausgeführt wird e nach 1 Millisekunde.Die schnellste Ausführung der Funktion ist ungefähr 9 Millisekunden (und zwar basierend auf den Interna des Browsers), aber in Wirklichkeit kann sie die Funktion nicht ausführen, solange kein anderer Code läuft.

Bei der unerwarteten Ausgabe tritt dieses Verhalten auf, da timeoutFunction einen Verweis auf die Variable x enthält, die in einem höheren Bereich deklariert ist. Dies bewirkt, dass ein closure um die x Variable erstellt wird, so dass jedes Mal, wenn die Funktion ausgeführt wird, keine Kopie von x für sich selbst erhält, aber den x Wert teilt, da es in einem höheren Bereich deklariert ist. Dies ist der klassische Nebeneffekt von Verschlüssen.

Es gibt ein paar Möglichkeiten, die Syntax anpassen um das Problem zu beheben ...

Machen Sie eine Kopie x und lassen jede Funktion eine eigene Kopie verwenden, indem x in die Funktion übergeben. Wenn Sie einen primitiven Typ (Boolean, Zahl, String) übergeben, wird eine Kopie der Daten erstellt und übergeben. Dies bricht die Abhängigkeit von dem gemeinsamen Bereich x. Ihr letztes Beispiel tut dies:

for (var x = 0; x < 5; x++) { 
 
    var timeoutFunction = function(x) { 
 
     return function() { 
 
      console.log(x) 
 
     } 
 
    } 
 
    // Because you are passing x into the function, a copy of x will be made for the 
 
    // function that is returned, that copy will be independent of x and a copy will 
 
    // be made upon each loop iteration. 
 
    setTimeout(timeoutFunction(x), 1) 
 
}

Ein weiteres Beispiel für die gleiche Sache tut würde erforderlich sein, wenn Ihre Timeout-Funktion nicht wurde eine weitere Funktion der Rückkehr (weil es keine Funktion wäre, den Wert zu übergeben zu). So ist dieses Beispiel wird eine zusätzliche Funktion:

for (var x = 0; x < 5; x++) { 
 
    
 
    // This time there is no nested function that will be returned, 
 
    function timeoutFunction(i) {  
 
      console.log(i);   
 
    } 
 
    
 
    // If we create an "Immediately Invoked Funtion Expression" (IIFE), 
 
    // we can have it pass a copy of x into the function it invokes, thus 
 
    // creating a copy that will be in a different scope than x. 
 
    (function(i){   
 
     setTimeout(function(){ 
 
     timeoutFunction(i); // i is now a copy of x 
 
     }, 1); 
 
    }(x)); 
 
}

Wenn Sie mit Browsern arbeiten, den ECMAScript 2015-Standard unterstützen, können Sie einfach die var x Erklärung in der Schleife ändern let x, so dass x erhält Blockebene Umfang bei jeder Iteration der Schleife:

// Declaring a variable with let causes the variable to have "block-level" 
 
// scope. In this case the block is the loop's contents and for each iteration of the 
 
// loop. Upon each iteration, a new "x" will be created, so a different scope from 
 
// the old "x" is what's used. 
 
for (let x = 0; x < 5; x++) { 
 
    var timeoutFunction = function() { 
 
     return function() { 
 
      console.log(x) 
 
     } 
 
    } 
 
    setTimeout(timeoutFunction(), 1) 
 
}

+0

Danke für die tollen Beispiele und Links! Zuerst konnte ich die Bemerkung zu den Schließungen nicht vollständig verstehen. Die Referenz machte es klarer und dann verstand ich genau, was du meintest :) "Der Grund dafür ist, dass die zugewiesene Funktion eine Schließung ist; sie besteht aus der Funktionsdefinition und der erfassten Umgebung aus dem Funktionsumfang. Fünf Schließungen wurden gemacht erstellt von der Schleife, aber jeder teilt die gleiche einzelne Umgebung, die eine Variable mit sich ändernden Wert "x" hat. Wenn die setTimeout-Callbacks ausgeführt werden, bewirkt das Zugreifen auf "x" in diesem Moment " – jervtub

+0

@jervtubu Gern geschehen. Verschlüsse sind in der Regel eines der schwierigeren Dinge, um Ihre Meinung zu bekommen und sie brauchen ein wenig Übung zu verstehen. Vergessen Sie nicht, als Antwort zu markieren. Viel Glück! –

1

Sie haben für Ihre Funktion neue Möglichkeiten zu schaffen, um Erinnern Sie sich an den Wert aus der gegebenen Iteration:

for (var x = 0; x < 5; x++) { 
    var timeoutFunction = (function(x) { 
     return function() { 
      console.log(x) 
     } 
    })(x) 
    setTimeout(timeoutFunction(), 1) 
} 

Eine andere Lösung ist ES2015 zu verwenden let:

for (let x = 0; x < 5; x++) { 
    var timeoutFunction = function() { 
      console.log(x) 
     } 
    setTimeout(timeoutFunction(), 1) 
} 
+0

Danke für Ihre Antwort Bartekfr. Ich suchte wirklich nach spezifischen Informationen, warum der Bereich innerhalb der for-Schleife fehlgeschlagen ist, daher habe ich dies nicht als die akzeptierte Antwort festgelegt. Ich mag den Verweis auf "let x". – jervtub

1

Verwendung dieser Ausschnitt

for(let x = 0; x < 5; x++) { 
    var timeoutFunction = function() { 
     console.log(x); 
    }; 
    setTimeout(timeoutFunction, 1); 
}; 
console.log('For loop completed'); 

JavaScript Multi ist nicht eingefädelt, so mit Ihrem Code die timeoutFunction nach der for-Schleife abgeschlossen ist ausgeführt wird, da Sie verwenden die globale Variable (in Bezug auf den timeoutFunction-Kontext) das Endergebnis wird gedruckt

+0

Ihre Lösung ist nicht korrekt: weil timeoutFunction nicht zurückgibt Funktion 'console.log' wird sofort ohne Verzögerung aufgerufen. Um es zu testen erhöhen Timeout bis 15000 und Sie werden sehen, dass die Zahl ohne Verzögerung –

+0

den Code nach dem Test – Raj

+0

repariert Danke Raj für Ihre Antwort! Singlethread zu sein erklärt für mich die Verzögerung der Protokollierung nach dem Durchlaufen von "1000000" Ganzzahlen. :) Ich mag den Verweis auf "let x". – jervtub