Animationen unter single-threaded JavaScript

7

JavaScript ist eine einzelne Thread-Sprache und führt daher einen Befehl nach dem anderen aus. Asynchrone Programmierung wird über Web-APIs ( DOM für die Ereignisbehandlung, XMLHttpRequest für AJAX-Aufrufe, WindowTimers für setTimeout ) und die Ereigniswarteschlange implementiert verwaltet vom Browser. So weit, ist es gut! Betrachten Sie nun den folgenden sehr einfachen Code:

%Vor%

Könnte mir bitte jemand den zugrunde liegenden Mechanismus erklären? Da .hide () noch nicht beendet ist (die Animation dauert 17 Sekunden) und die JS - Engine sich damit beschäftigt und sie in der Lage ist, jeweils einen Befehl auszuführen, wie geht es dann in die nächste Zeile und führt den Befehl weiter aus verbleibender Code?

Wenn Ihre Antwort ist, dass die Animation Versprechungen erzeugt, bleibt die Frage gleich: Wie verhält sich JavaScript mit mehreren Dingen gleichzeitig (Ausführen der Animation selbst? , im Falle von Versprechungen die Animationswarteschlange beobachten und mit dem folgenden Code fortfahren ...).

Außerdem kann ich nicht erklären, wie Versprechen in jQuery funktionieren, wenn sie ihr übergeordnetes Objekt überwachen müssen, bis es aufgelöst oder zurückgewiesen wird , was Codeausführung bedeutet und gleichzeitig Zeit wird der verbleibende Code ausgeführt. Wie ist das bei einem Single-Thread-Ansatz möglich? Ich habe kein Problem zu verstehen, AJAX fordert, dass ich weiß, dass sie von der JS-Engine weggenommen werden ...

    
Unknown developer 12.02.2016, 12:16
quelle

4 Antworten

18

tl; dr; in einer streng single-threaded Umgebung ohne fremde Hilfe wäre das nicht möglich.

Ich glaube, ich verstehe dein Problem. Lassen Sie uns ein paar Dinge aus dem Weg räumen:

JavaScript ist immer synchron

In der Sprachspezifikation sind keine asynchronen APIs definiert. Alle Funktionen wie Array.prototype.map oder String.fromCharCode laufen immer synchron *.

Code wird immer vollständig ausgeführt. Code hört nicht auf zu laufen, bis er von einem return , einem impliziten return (das Ende des Codes erreicht) oder einem throw (abrupt) beendet wird.

%Vor%

JavaScript lebt in einer Plattform

Die JavaScript-Sprache definiert ein Konzept namens Hostumgebung :

  

Auf diese Weise soll das bestehende System eine Host-Umgebung von Objekten und Einrichtungen bereitstellen, die die Fähigkeiten der Skriptsprache vervollständigt.

Die Hostumgebung, in der JavaScript im Browser ausgeführt wird, wird als DOM oder Dokumentobjektmodell bezeichnet. Es gibt an, wie Ihr Browserfenster mit der JavaScript-Sprache interagiert. In NodeJS zum Beispiel ist die Host-Umgebung völlig anders.

Während alle JavaScript-Objekte und -Funktionen synchron zur Fertigstellung ausgeführt werden, kann die Hostumgebung eigene Funktionen bereitstellen, die nicht unbedingt in JavaScript definiert sind. Sie haben nicht die gleichen Einschränkungen wie der Standard-JavaScript-Code und kann verschiedene Verhaltensweisen definieren - zum Beispiel ist das Ergebnis von document.getElementsByClassName ein Live-DOM NodeList , die sich sehr von Ihrem normalen JavaScript-Code unterscheidet:

%Vor%

Einige dieser Host-Funktionen müssen E / A-Vorgänge wie Zeitplan-Timer ausführen, Netzwerkanforderungen ausführen oder Dateizugriff durchführen. Diese APIs müssen wie alle anderen APIs vollständig ausgeführt werden . Diese APIs werden von der Hostplattform aufgerufen - sie rufen Funktionen auf, die Ihr -Code nicht besitzt - normalerweise (aber nicht unbedingt) sind sie in C ++ geschrieben und verwenden Threading- und Betriebssystemfunktionen für die gleichzeitige und gleichzeitige Ausführung von Objekten parallel. Dieser Nebenläufigkeit kann nur Hintergrundarbeit (wie das Planen eines Timers) oder tatsächliche Parallelität (wie WebWorker <) sein / a> - wieder Teil des DOM und nicht JavaScript).

Wenn Sie also Aktionen im DOM wie setTimeout aufrufen oder eine Klasse anwenden, die CSS-Animation verursacht, ist sie nicht an die gleichen Anforderungen gebunden wie Ihr Code. Es kann Threading oder das Betriebssystem async io verwenden.

Wenn Sie etwas tun wie:

%Vor%

Was tatsächlich passiert ist:

  • Die Hostfunktion setTimeout wird mit einem Parameter vom Typ function aufgerufen. Es verschiebt die Funktion in eine Warteschlange in der Hostumgebung .
  • das console.log("Hello") wird synchron ausgeführt.
  • Alle anderen synchronen Codes werden ausgeführt (beachten Sie, dass der setTimeout-Aufruf hier vollständig synchron war).
  • JavaScript fertig ausgeführt - Steuerung wird in die Host-Umgebung übertragen.
  • Die Host-Umgebung bemerkt, dass sie etwas in der Timer-Warteschlange hat und genug Zeit vergangen ist, so dass sie ihr Argument (die Funktion) aufruft - console.log("World") wird ausgeführt.
  • Der gesamte andere Code in der Funktion wird synchron ausgeführt.
  • Die Kontrolle wird an die Host-Umgebung (Plattform) zurückgegeben.
  • Etwas anderes passiert in der Host-Umgebung (Mausklick, AJAX-Anfrage zurück, Timer-Feuern). Die Host-Umgebung ruft den Handler auf, den der Benutzer an diese Aktionen übergeben hat.
  • Wieder wird JavaScript synchron ausgeführt.
  • Und so weiter und so weiter ...

Ihr spezifischer Fall

%Vor%

Hier wird der Code synchron ausgeführt. Der vorhergehende Befehl hat beendet, aber er hat eigentlich nicht viel getan - stattdessen hat er einen Callback auf der Plattform a geplant (in .hide(17000) und dann wieder console.log ausgeführt) - wird der gesamte JavaScirpt-Code ausgeführt synchron immer.

Das ist - hide führt nur sehr wenig Arbeit aus und läuft für einige Millisekunden und plant dann mehr Arbeit, die später erledigt werden muss. Es wird nicht 17 Sekunden lang ausgeführt.

Nun sieht die Implementierung von hide so aus:

%Vor%

Im Grunde genommen ist unser Code single threaded - er fordert die Plattform auf, ihn 60 Mal pro Sekunde aufzurufen und macht das Element jedes Mal etwas weniger sichtbar. Alles wird immer bis zum Ende ausgeführt, aber abgesehen von der ersten Codeausführung ruft der Plattformcode (die Host-Umgebung) unseren Code auf, mit Ausnahme von umgekehrt.

Die eigentliche, einfache Antwort auf Ihre Frage ist also, dass das Timing der Berechnung von Ihrem Code "weggenommen" wird, ähnlich wie bei einer AJAX-Anfrage. Um es direkt zu beantworten:

In einer Umgebung mit einem einzelnen Thread wäre es ohne Hilfe von außen nicht möglich.

Dies ist das umschließende System, das entweder Threads oder asynchrone Funktionen des Betriebssystems verwendet - unsere Host-Umgebung. In reinem Standard-ECMAScript wäre das nicht möglich.

* Mit der ES2015-Einfügung von Versprechungen delegiert die Sprache Aufgaben zurück an die Plattform (Host-Umgebung) - aber das ist eine Ausnahme.

    
Benjamin Gruenbaum 18.02.2016, 23:14
quelle
2

Sie haben mehrere Arten von Funktionen in Javascript: Blockieren und nicht blockieren.

Die nicht blockierende Funktion kehrt sofort zurück und die Ereignisschleife setzt die Ausführung fort, während sie im Hintergrund arbeitet und darauf wartet, die Callback-Funktion aufzurufen (wie Ajax verspricht).

Die Animation basiert auf setInterval und / oder setTimeout und diese beiden Methoden geben sofort den Code wieder verfügbar. Der Rückruf wird zurück in den Ereignisschleifenstapel geschoben, ausgeführt und die Hauptschleife wird fortgesetzt.

Ich hoffe, das wird helfen.

  

Weitere Informationen erhalten Sie hier oder hier

    
Unex 15.02.2016 13:06
quelle
0

Ereignisschleife

JavaScript verwendet eine so genannte Ereignisschleife . Die Ereignisschleife ist wie eine while(true) Schleife.

Um es zu vereinfachen, nehmen wir an, dass JavaScript ein riesiges Array hat, in dem alle Ereignisse gespeichert sind. Die Ereignisschleife durchläuft diese Ereignisschleife, beginnend mit dem ältesten Ereignis bis zum neuesten Ereignis. Das heißt, JavaScript macht so etwas:

%Vor%

Wenn während der Verarbeitung des Ereignisses ( event.process ) ein neues Ereignis ausgelöst wird (nennen wir dies eventA ), wird das neue Ereignis in eventsArray gespeichert und die Ausführung des aktuellen Ereignisses wird fortgesetzt. Wenn das aktuelle Ereignis verarbeitet ist, wird das nächste Ereignis verarbeitet und so weiter, bis wir eventA erreichen.

Kommen Sie zu Ihrem Beispielcode,

%Vor%

Wenn die erste Zeile ausgeführt wird, wird ein Ereignis-Listener erstellt und ein Timer gestartet. Angenommen, jQuery verwendet 100ms Frames. Ein Timer von 100ms wird mit einer Callback-Funktion erstellt. Der Timer startet im Hintergrund (die Implementierung erfolgt intern im Browser), während die Kontrolle an Ihr Skript zurückgegeben wird. Während der Timer im Hintergrund ausgeführt wird, wird das Skript weiterhin in der zweiten Zeile angezeigt. Nach 100 ms wird der Timer beendet und ein Ereignis ausgelöst. Dieses Ereignis wird oben in eventsArray gespeichert und nicht sofort ausgeführt. Sobald der Code ausgeführt wurde, überprüft JavaScript das eventsArray und sieht, dass es ein neues Ereignis gibt, und führt es dann aus.

Das Ereignis wird dann ausgeführt, und Ihr div- oder anderes Element bewegt sich um ein paar Pixel und ein neuer 100ms-Timer wird gestartet.

Bitte beachten Sie, dass dies eine Vereinfachung ist, nicht das tatsächliche Funktionieren der ganzen Sache. Es gibt ein paar Komplikationen für die ganze Sache, wie der Stapel und alle. Weitere Informationen finden Sie im MDN-Artikel hier .

    
Kizer 18.02.2016 13:26
quelle
0
  

Könnte mir jemand bitte den zugrunde liegenden Mechanismus der   über? Da .hide () noch nicht beendet ist (die Animation dauert 17   Sekunden) und JS Engine ist damit beschäftigt und es ist in der Lage   Ausführen eines Befehls zu einer Zeit, auf welche Weise geht es zum nächsten   Linie und fährt fort, den restlichen Code zu laufen?

jQuery.fn.hide() ruft intern jQuery.fn.animate auf, das jQuery.Animation aufruft, was ein jQuery-Objekt deferred.promise() zurückgibt; Siehe auch jQuery.Deferred()

  

Die Methode deferred.promise() ermöglicht eine asynchrone Funktion   verhindern, dass anderer Code den Fortschritt oder den Status des Codes beeinträchtigt   interne Anfrage.

Zur Beschreibung von Promise siehe Versprechen / A + , Versprechen-Entpacken , Grundlegender JavaScript-Versprechen-Implementierungsversuch ; Was ist Node.js?

jQuery.fn.hide :

%Vor%

jQuery.fn.animate :

%Vor%

jQuery.Animation :

%Vor%

Wenn .hide() aufgerufen wird, wird ein jQuery.Deferred() erstellt, das die Animationsaufgaben verarbeitet.

Dies ist der Grund, warum console.log() aufgerufen wird.

Wenn include start -Option von .hide() überprüfen kann, dass .hide() beginnt, bevor console.log() in der nächsten Zeile aufgerufen wird, blockiert die Benutzeroberfläche jedoch nicht, asynchrone Tasks auszuführen.

%Vor% %Vor%

Native Promise Implementierung

%Vor% %Vor% %Vor%
    
guest271314 19.02.2016 00:29
quelle