2016-07-30 24 views
3

Things (ich glaube) verstehenReihenfolge der Ereignisse in JS Ereigniswarteschlange

Trotz der Tatsache, dass moderne Browser Multi-Threaded sind, ist JavaScript Single-Thread ausgeführt und es wird garantiert synchron ausgeführt werden. Das heißt, wenn der Aufrufstapel eingegeben wird, läuft der JS ununterbrochen, bis der Aufrufstapel wieder leer wird. Insbesondere ist sichergestellt, dass keine zwei JS-Skripte gleichzeitig ausgeführt werden.

JavaScript ist jedoch ereignisgesteuert. Gemäß der description of the concurrency model at MDN führt der Browser eine Warteschlange von Ereignissen. Im Leerlauf nimmt der Browser das erste Ereignis aus der Warteschlange und führt das zugehörige JS-Objekt (falls vorhanden) aus. Nachdem das Skript beendet wurde, erlangt der Browser wieder die Kontrolle und fährt mit dem nächsten Ereignis fort. Während ein JS-Skript ausgeführt wird, macht der Browser nichts anderes, insbesondere blockiert es die Benutzeroberfläche. Letzterer ist verantwortlich für die berüchtigte "langlaufende Skript-Warnung".

Es gibt einige wirklich asynchrone Funktionen wie setTimeout, die vom Laufzeitsystem (auch bekannt als Browser) zur Verfügung gestellt werden. Sie können verwendet werden, um ein neues Ereignis in die Ereigniswarteschlange einzufügen, und der zugehörige Rückruf wird auf den Aufrufstapel gesetzt, wenn das Ereignis ausgelöst wurde und der Aufrufstapel leer ist. Ein Entwickler kann seine "eigenen" asynchronen Funktionen nicht erstellen, ohne eine der nativen asynchronen Funktionen zu verwenden.

Dies gesagt, setTimeout(someFunction, 0) bietet eine Option zum Aufteilen eines lang laufenden Stück Code, die sonst die "lang laufende Skript Warnung" in kleinere Stücke auslösen würde. Wenn der Aufruf-Stack leer wird, kann der Browser andere Ereignisse (wie die Benutzeroberfläche) einholen, bevor er das nächste Stück Code ausführt.

Ein Beispiel

Um zu überprüfen, ob ich recht habe ich erstellt dieses piece of code

"use strict"; 
for(var i = 0; i != 5; i++) { 
    confirm('This is #: ' + i); 
    setTimeout(
    (function(j) { 
     return function() { confirm('This is #: ' + j); }; 
    })(i+10), 
    0 
); 
} 

Erwartete Ergebnisse

ich die Alertbox erwartet in der richtigen Reihenfolge zu kommen: 0 , 1, 2, 3, 4, 10, 11, 12, 13 und 14. Meine Idee war, dass bei jeder Iteration die folgenden zwei Schritte passieren: (a) Die erste Bestätigungsbox wird syn angezeigt chronisch. (b) Ein neues Ereignis wird an das Ende der Ereigniswarteschlange gesendet. Der Callback wird jedoch noch nicht aufgerufen, sondern zurückgestellt, da der Aufruf-Stack nicht leer ist, da die Schleife noch läuft.

Daher erwartete ich, die Warnungen 0, 1, 2, 3, 4 zuerst in dieser Reihenfolge zu sehen, weil dies Teil der synchronen Schleife ist. Dann endet die Schleife und der Aufrufstapel wird klar. Das nächste Ereignis wird aus der Ereigniswarteschlange genommen, bei der es sich um das erste asynchrone Zeitlimit handelt. Also erwartete ich, 10 als nächstes zu sehen. Nach dem Schließen des Bestätigungsdialogs wird der Call Stack wieder gelöscht. Also habe ich erwartet, 11 als nächstes zu sehen. Und so weiter.

Die tatsächlichen Ergebnisse

Die Alarmboxen werden in gemischter Reihenfolge gezeigt, z.B. 0, 1, 2, 10, 11, 3, 12, 4, 13, 14

Frage

Warum sind die Alertbox in gemischter Reihenfolge? Wenn mindestens der synchrone Teil (0, 1, 2, 3, 4) im Block angezeigt wurde und nur die Rückrufe vertauscht waren, hätte ich akzeptiert, dass die Ereigniswarteschlange keine Reihenfolge des Ereignisses garantiert.Es scheint jedoch, dass sogar die synchrone Schleife unterbrochen und mit den asynchronen Ereignissen verschachtelt wird. Ich dachte, das wird garantiert nicht passieren. Was geht hier vor sich?

+0

Welchen Browser testen Sie? Wenn ich deine Geige in Chrome laufe, scheint es durchgängig (ich habe es etwa zehnmal probiert) zuerst 0, 1, 2, 3, 4 zu zeigen, aber dann ändert sich die Reihenfolge der> = 10 Eingabeaufforderungen. (Beachten Sie, dass Sie, wenn Sie 'console.log()' anstelle von 'confirm()' verwenden, die erwartete Reihenfolge erhalten.) – nnnnnn

+0

Ich bin momentan nicht in der Lage dies zu testen, aber in Einige Browser (Firefox sowieso), die Funktionen wie "alert" und "confirm" blockieren, sind semi-asynchron (die Benutzeroberfläche wird nicht blockiert und einige Ereignisse werden ausgelöst). Vielleicht ist das der Grund? –

+0

Werfen Sie einen Blick auf diese aktualisierte Geige: https://jsfiddle.net/5kzv973x/4/ - der Browser nutzt die Gelegenheit, den Bildschirm neu zu streichen, während Sie darauf warten, dass Sie die 'confirm()' schließen. – nnnnnn

Antwort

3

Dies ist, weil in einigen Browsern (Firefox in Ihrem Fall) die traditionell blockierenden Funktionen alert, confirm und prompt die Ereigniswarteschlange nicht stoppen. Sie beenden die Ausführung des aktuellen Codeabschnitts, bis der Benutzer mit ihm interagiert, aber die Schleife wird weiterhin ausgeführt, und einige JavaScript-Ereignisse werden weiterhin ausgelöst (nicht alle Ereignisse).

Ist das ein Firefox-Bug?

Nein, Ihr Code hat nur undefiniertes Verhalten.

In the spec, Pausieren ist optional:

Optional Pause, während für den Benutzer warten, um die Meldung zu bestätigen.

Firefox wählt nicht, um die Benutzererfahrung zu verbessern.

Ein weiteres Beispiel:

Hier ist ein einfaches Beispiel zeigt, wie die Ereignisse der Größe noch Feuers. Dies zeigt, wie Sie nicht sicher sein können, dass eine für einen Event-Handler zugängliche Variable nicht geändert wird, während ein alert-Typ-Dialog geöffnet ist.

(function(){ 
    'use strict'; 
    var resized = false; 
    window.addEventListener('resize', function() { 
     resized = true; 
     console.log('resizing'); 
    }); 
    alert('resize the window before continuing'); 
    console.log('Resize events fired while alert open?: ' + resized); 
})(); 
+0

Das bedeutet aber auch, dass die einfache Regel "keine zwei JS-Skripte werden gleichzeitig ausgeführt" nicht in allen Situationen gilt. Wenn im obigen Beispiel die äußere Schleife noch läuft, aber bei einem der synchronen Alerts() blockiert ist und dann mit den asynchronen Callbacks verschachtelt wird, kann es andere Szenarien geben, in denen diese Callbacks Variablen des schlafenden Codeteils modifizieren und somit können verursacht all die bekannten Nebenläufigkeitsprobleme. Habe ich recht? – user2690527

+1

@ user2690527 Nicht genau. Ja, eine Variable kann geändert werden, während die 'Warnung' offen ist, die Spezifikation verbietet dies nicht (Ich habe ein Beispiel zu meiner Antwort hinzugefügt). Dies bedeutet jedoch nicht, dass technisch zwei Codeteile gleichzeitig ausgeführt werden, die Ausführung des pausierten wird ausgesetzt, bis der Dialog vom Typ "Alarm" geschlossen und zur Warteschlange hinzugefügt wird. Keine 2 Code-Teile werden gleichzeitig ausgeführt, was hauptsächlich nicht garantiert ist. Es gibt auch andere Möglichkeiten, um anderen Code unerwartet auszulösen, wie den Aufruf von '.click()', der 'onclick'-Handler auslöst. –