Diese beiden am besten bewerteten Antworten sind beide falsch. Sehen Sie sich die MDN-Beschreibung des Gleichzeitigkeitsmodells und der Ereignisschleife an und es sollte klar werden, was vor sich geht (die MDN-Ressource ist ein echtes Juwel). Und einfach mit setTimeout
kann neben der "Lösung" dieses kleinen Problems auch unerwartete Probleme in Ihrem Code verursachen.
Was ist eigentlich Hier geht es nicht darum, dass "der Browser vielleicht noch nicht ganz fertig ist, weil die Gleichzeitigkeit nicht gegeben ist", oder um etwas, das auf "jede Zeile ist ein Ereignis, das am Ende der Warteschlange hinzugefügt wird" basiert.
El jsfiddle von DVK veranschaulicht zwar ein Problem, aber seine Erklärung dafür ist nicht korrekt.
Was in seinem Code passiert, ist, dass er zuerst einen Event-Handler an die click
Ereignis auf der #do
Taste.
Wenn Sie dann tatsächlich auf die Schaltfläche klicken, wird ein message
erstellt, die auf die Ereignisbehandlungsfunktion verweist, die der Datei message queue
. Wenn die event loop
diese Nachricht erreicht, erstellt es eine frame
auf dem Stapel, mit dem Funktionsaufruf des Click-Event-Handlers im jsfiddle.
Und genau hier wird es interessant. Wir sind so daran gewöhnt, Javascript als asynchron zu betrachten, dass wir dazu neigen, diese kleine Tatsache zu übersehen: Jeder Frame muss vollständig ausgeführt werden, bevor der nächste Frame ausgeführt werden kann. . Keine Gleichzeitigkeit, Leute.
Was bedeutet das? Es bedeutet, dass jede Funktion, die von der Nachrichtenwarteschlange aus aufgerufen wird, die Warteschlange blockiert, bis der von ihr erzeugte Stapel geleert wurde. Oder, allgemeiner ausgedrückt, sie blockiert, bis die Funktion zurückgekehrt ist. Und sie blockiert alles einschließlich DOM-Rendering-Operationen, Scrollen und dergleichen. Wenn Sie eine Bestätigung wünschen, versuchen Sie einfach, die Dauer der lang laufenden Operation im Fiddle zu verlängern (z. B. lassen Sie die äußere Schleife noch 10 Mal laufen), und Sie werden feststellen, dass Sie die Seite nicht mehr scrollen können, während sie läuft. Wenn er lange genug läuft, wird Ihr Browser Sie fragen, ob Sie den Prozess beenden wollen, weil die Seite nicht mehr reagiert. Der Frame wird ausgeführt, und die Ereignisschleife und die Nachrichtenwarteschlange bleiben stecken, bis er beendet ist.
Warum also dieser Nebeneffekt, dass der Text nicht aktualisiert wird? Denn während Sie haben den Wert des Elements im DOM geändert - Sie können console.log()
seinen Wert unmittelbar nach der Änderung und sehen, dass er hat geändert wurde (was zeigt, warum DVKs Erklärung nicht korrekt ist) - der Browser wartet darauf, dass der Stack aufgebraucht ist (die on
Handler-Funktion zurückzugeben) und damit die Nachricht zu beenden, so dass sie schließlich die Nachricht ausführen kann, die von der Laufzeit als Reaktion auf unsere Mutationsoperation hinzugefügt wurde, und um diese Mutation in der Benutzeroberfläche widerzuspiegeln.
Der Grund dafür ist, dass wir darauf warten, dass der Code fertiggestellt wird. Wir haben nicht gesagt: "Jemand holt das und ruft dann diese Funktion mit den Ergebnissen auf, danke, und jetzt bin ich fertig, also kehre ich zurück, mach was auch immer jetzt", wie wir es normalerweise mit unserem ereignisbasierten asynchronen Javascript tun. Wir geben eine Click-Event-Handler-Funktion ein, wir aktualisieren ein DOM-Element, wir rufen eine andere Funktion auf, die andere Funktion arbeitet lange und kehrt dann zurück, wir aktualisieren dann das gleiche DOM-Element, und dann kehren wir von der Ausgangsfunktion zurück, wodurch der Stapel effektiv geleert wird. Und dann kann der Browser zur nächsten Nachricht in der Warteschlange gelangen, bei der es sich durchaus um eine Nachricht handeln kann, die von uns durch Auslösen eines internen Ereignisses vom Typ "on-DOM-mutation" erzeugt wurde.
Die Benutzeroberfläche des Browsers kann die Benutzeroberfläche nicht aktualisieren (oder will es nicht), bis der aktuell ausgeführte Frame abgeschlossen ist (die Funktion zurückgekehrt ist). Ich persönlich denke, dass dies eher durch Design als durch Beschränkung bedingt ist.
Warum ist die setTimeout
Sache dann funktionieren? Es funktioniert, weil es den Aufruf der langlaufenden Funktion effektiv aus seinem eigenen Frame entfernt und die Ausführung zu einem späteren Zeitpunkt in der window
Kontext, so dass sie selbst sofort zurückkehren und erlauben der Nachrichtenwarteschlange, andere Nachrichten zu verarbeiten. Und die Idee ist, dass die UI "auf Update"-Nachricht, die von uns in Javascript ausgelöst wurde, wenn der Text im DOM zu ändern ist jetzt vor der Nachricht für die lang laufende Funktion in der Warteschlange, so dass die UI-Aktualisierung geschieht, bevor wir für eine lange Zeit blockieren.
Beachten Sie, dass a) die langlaufende Funktion noch blockiert alles, wenn es ausgeführt wird, und b) Sie sind nicht garantiert, dass das UI-Update tatsächlich vor ihm in der Nachrichtenwarteschlange ist. In meinem Chrome-Browser vom Juni 2018 wird ein Wert von 0
behebt nicht das Problem, das die Fiddle demonstriert - 10 schon. Ich bin tatsächlich ein wenig erstickt durch diese, denn es scheint logisch für mich, dass die UI-Update-Nachricht sollte vor ihr in der Warteschlange sein, da seine Auslöser vor der Planung der lang laufenden Funktion ausgeführt wird, um "später" ausgeführt werden. Aber vielleicht gibt es einige Optimierungen in der V8-Engine, die stören können, oder vielleicht ist mein Verständnis einfach nicht ausreichend.
Okay, was ist also das Problem bei der Verwendung von setTimeout
und was ist die bessere Lösung für diesen speziellen Fall?
Zunächst einmal ist das Problem bei der Verwendung von setTimeout
auf jeden Event-Handler wie diesen, um zu versuchen, ein anderes Problem zu lindern, ist anfällig für die Verwirrung mit anderen Code. Hier ist ein Beispiel aus dem wirklichen Leben aus meiner Arbeit:
Ein Kollege, in einem falsch informierten Verständnis über die Ereignisschleife, versucht zu "Thread" Javascript, indem einige Vorlage Rendering-Code verwenden setTimeout 0
für seine Darstellung. Er ist nicht mehr hier, um zu fragen, aber ich kann vermuten, dass er vielleicht Timer eingefügt hat, um die Rendering-Geschwindigkeit zu messen (was die Unmittelbarkeit der Rückgabe von Funktionen wäre) und festgestellt hat, dass die Verwendung dieses Ansatzes für blitzschnelle Antworten von dieser Funktion sorgen würde.
Das erste Problem liegt auf der Hand: Sie können kein JavaScript-Threading durchführen, so dass Sie hier nichts gewinnen, während Sie eine Verschleierung hinzufügen. Zweitens haben Sie jetzt effektiv das Rendern einer Vorlage von dem Stapel möglicher Ereignis-Listener abgetrennt, die erwarten könnten, dass genau diese Vorlage gerendert wurde, während sie es vielleicht gar nicht war. Das tatsächliche Verhalten dieser Funktion ist nun nicht mehr deterministisch, ebenso wie - ohne es zu wissen - jede Funktion, die sie ausführt oder von ihr abhängt. Man kann fundierte Vermutungen anstellen, aber man kann das Verhalten nicht richtig codieren.
Die "Lösung" beim Schreiben eines neuen Ereignis-Handlers, der von seiner Logik abhing, bestand darin, dass auch verwenden. setTimeout 0
. Aber das ist keine Lösung, es ist schwer zu verstehen, und es macht keinen Spaß, Fehler zu beheben, die durch solchen Code verursacht werden. Manchmal gibt es überhaupt kein Problem, ein anderes Mal scheitert es ständig, und dann wieder funktioniert es manchmal und bricht sporadisch ab, je nach aktueller Leistung der Plattform und was auch immer sonst gerade los ist. Aus diesem Grund würde ich persönlich davon abraten, diesen Hack zu verwenden (er ist ein Hack, und wir sollten alle wissen, dass es einer ist), es sei denn, man weiß wirklich, was man tut und welche Konsequenzen das hat.
Was aber kann wir stattdessen tun? Nun, wie der zitierte MDN-Artikel vorschlägt, können Sie entweder die Arbeit in mehrere Nachrichten aufteilen (wenn Sie können), so dass andere Nachrichten, die in der Warteschlange stehen, mit Ihrer Arbeit verschachtelt und ausgeführt werden können, während sie läuft, oder Sie verwenden einen Webworker, der parallel zu Ihrer Seite läuft und Ergebnisse zurückgibt, wenn er mit seinen Berechnungen fertig ist.
Oh, und wenn Sie jetzt denken: "Könnte ich nicht einfach einen Rückruf in die lang laufende Funktion einfügen, um sie asynchron zu machen?", dann nein. Der Callback macht die Funktion nicht asynchron, sie muss immer noch den langlaufenden Code ausführen, bevor sie explizit Ihren Callback aufruft.
2 Stimmen
Die meisten Fragen beschreiben, warum es nützlich ist. Wenn Sie wissen wollen, warum das so ist, lesen Sie meine Antwort: stackoverflow.com/a/23747597/1090562
35 Stimmen
Philip Roberts erklärt dies in seinem Vortrag "What the heck is the event loop?" auf die bestmögliche Weise. youtube.com/watch?v=8aGhZQkoFbQ
4 Stimmen
Wenn Sie es eilig haben, hier beginnt der Teil des Videos, in dem er genau auf die Frage eingeht: youtu.be/8aGhZQkoFbQ?t=14m54s . Trotzdem ist das ganze Video auf jeden Fall sehenswert.
8 Stimmen
setTimeout(fn)
ist identisch mitsetTimeout(fn, 0)
Übrigens.0 Stimmen
Relevant für diese Frage ist die Methode queueMicrotask() , die später eingeführt wurde.