Laden und Ausführen der Reihenfolge der Skripte

265

Es gibt so viele verschiedene Möglichkeiten, JavaScript in eine HTML-Seite aufzunehmen. Ich kenne die folgenden Optionen:

  • Inline-Code oder von einem externen URI geladen
  • im <head> - oder <body> -Tag enthalten [ 1 , 2 ]
  • mit keinem deferoder asyncAttribut (nur externe Skripte)
  • in der statischen Quelle enthalten oder dynamisch von anderen Skripten hinzugefügt (in unterschiedlichen Analysezuständen, mit unterschiedlichen Methoden)

Ohne Browserskripte von der Festplatte, Javascript: URIs und onEvent-attributes [ 3 ], gibt es bereits 16 Alternativen, um JS auszuführen , und ich bin sicher, dass ich etwas vergessen habe.

Ich bin nicht so sehr mit dem schnellen (parallelen) Laden beschäftigt, sondern eher neugierig auf die Ausführungsreihenfolge (die von der Ladereihenfolge und der Dokumentreihenfolge abhängen kann ). Gibt es eine gute (browserübergreifende) Referenz, die wirklich alle Fälle abdeckt? ZB http://www.websiteoptimization.com/speed/tweak/defer/ befasst sich nur mit 6 von ihnen und testet meistens alte Browser.

Da ich befürchte, dass dies nicht der Fall ist, ist hier meine spezielle Frage: Ich habe einige (externe) Head-Skripte zum Initialisieren und Laden von Skripten. Dann habe ich zwei statische Inline-Skripte am Ende des Körpers. Mit dem ersten kann der Skriptlader ein anderes Skriptelement (das auf externe js verweist) dynamisch an den Body anhängen. Das zweite der statischen Inline-Skripte möchte js aus dem hinzugefügten externen Skript verwenden. Kann es sich darauf verlassen, dass der andere hingerichtet wurde (und warum :-)?

Bergi
quelle
Haben Sie sich das Laden von Skripten ohne Blockierung von Steve Souders angesehen? Es ist jetzt etwas veraltet, enthält aber dennoch einige wertvolle Einblicke in das Browserverhalten bei einer bestimmten Technik zum Laden von Skripten.
Josh Habdas

Antworten:

331

Wenn Sie nicht dynamisch Scripts geladen werden oder markieren sie als deferoder asyncwerden dann Skripts in der Reihenfolge , in der Seite aufgetreten geladen. Es spielt keine Rolle, ob es sich um ein externes Skript oder ein Inline-Skript handelt - sie werden in der Reihenfolge ausgeführt, in der sie auf der Seite vorkommen. Inline-Skripte, die nach externen Skripten kommen, werden gehalten, bis alle externen Skripte, die vor ihnen kamen, geladen und ausgeführt wurden.

Asynchrone Skripte (unabhängig davon, wie sie als asynchron angegeben werden) werden geladen und in einer unvorhersehbaren Reihenfolge ausgeführt. Der Browser lädt sie parallel und kann sie in beliebiger Reihenfolge ausführen.

Es gibt keine vorhersehbare Reihenfolge zwischen mehreren asynchronen Dingen. Wenn eine vorhersehbare Reihenfolge benötigt wird, muss diese codiert werden, indem für Ladebenachrichtigungen aus den asynchronen Skripten registriert und Javascript-Aufrufe manuell sequenziert werden, wenn die entsprechenden Dinge geladen werden.

Wenn ein Skript-Tag dynamisch eingefügt wird, hängt das Verhalten der Ausführungsreihenfolge vom Browser ab. In diesem Referenzartikel können Sie sehen, wie sich Firefox verhält . Kurz gesagt, die neueren Versionen von Firefox verwenden standardmäßig ein dynamisch hinzugefügtes Skript-Tag zu asynchron, sofern das Skript-Tag nicht anders festgelegt wurde.

Ein Skript-Tag mit asynckann ausgeführt werden, sobald es geladen wird. Tatsächlich kann der Browser den Parser von allen anderen Aktionen anhalten und das Skript ausführen. Es kann also wirklich fast jederzeit ausgeführt werden. Wenn das Skript zwischengespeichert wurde, wird es möglicherweise fast sofort ausgeführt. Wenn das Laden des Skripts eine Weile dauert, wird es möglicherweise ausgeführt, nachdem der Parser fertig ist. Das einzige, woran Sie sich erinnern sollten, asyncist, dass es jederzeit ausgeführt werden kann und diese Zeit nicht vorhersehbar ist.

Ein Skript-Tag mit deferwartet, bis der gesamte Parser fertig ist, und führt dann alle Skripte aus, die mit deferin der Reihenfolge markiert sind, in der sie angetroffen wurden. Auf diese Weise können Sie mehrere voneinander abhängige Skripte als markieren defer. Sie werden alle verschoben, bis der Dokumentparser fertig ist, aber sie werden in der Reihenfolge ausgeführt, in der sie angetroffen wurden, wobei ihre Abhängigkeiten erhalten bleiben. Ich denke, deferals würden die Skripte in eine Warteschlange gestellt, die nach Abschluss des Parsers verarbeitet wird. Technisch gesehen kann der Browser die Skripte jederzeit im Hintergrund herunterladen, aber sie werden den Parser erst ausführen oder blockieren, nachdem der Parser die Seite analysiert und Inline-Skripte analysiert und ausgeführt hat, die nicht markiert sind deferoder async.

Hier ist ein Zitat aus diesem Artikel:

In Skripte eingefügte Skripte werden asynchron in IE und WebKit ausgeführt, jedoch synchron in Opera und Firefox vor 4.0.

Der relevante Teil der HTML5-Spezifikation (für neuere kompatible Browser) ist hier . Dort ist viel über asynchrones Verhalten geschrieben. Offensichtlich gilt diese Spezifikation nicht für ältere Browser (oder fehlerhafte Browser), deren Verhalten Sie wahrscheinlich testen müssten, um festzustellen.

Ein Zitat aus der HTML5-Spezifikation:

Dann muss die erste der folgenden Optionen befolgt werden, die die Situation beschreiben:

Wenn das Element ein src-Attribut hat und das Element ein Defer-Attribut hat und das Element als "Parser-Insert" gekennzeichnet wurde und das Element kein asynchrones Attribut hat , muss das Element am Ende der Liste von hinzugefügt werden Skripte, die ausgeführt werden, wenn das Parsen des Dokuments abgeschlossen ist, das dem Dokument des Parsers zugeordnet ist, der das Element erstellt hat.

Die Aufgabe, die die Quelle der Netzwerkaufgabe nach Abschluss des Abrufalgorithmus in die Aufgabenwarteschlange stellt, muss das Flag "Bereit zur Ausführung durch den Parser" des Elements setzen. Der Parser übernimmt die Ausführung des Skripts.

Wenn das Element ein src-Attribut hat und das Element als "Parser eingefügt" gekennzeichnet wurde und das Element kein asynchrones Attribut hat Das Element ist das ausstehende Parsing-Blocking-Skript des Dokuments des Parsers, der das Element erstellt hat. (Es kann jeweils nur ein solches Skript pro Dokument vorhanden sein.)

Die Aufgabe, die die Quelle der Netzwerkaufgabe nach Abschluss des Abrufalgorithmus in die Aufgabenwarteschlange stellt, muss das Flag "Bereit zur Ausführung durch den Parser" des Elements setzen. Der Parser übernimmt die Ausführung des Skripts.

Wenn das Element kein Attribut src haben, und das Element markiert wurde als „Parser-eingefügt“ und das Dokument des HTML - Parser oder XML - Parser, der das Skript Element erstellt Blatt ein Stil, der Skripte blockiert Das Element ist die ausstehendes Parsing-Blocking-Skript des Dokuments des Parsers, der das Element erstellt hat. (Es kann jeweils nur ein solches Skript pro Dokument vorhanden sein.)

Setzen Sie das Flag "Bereit, vom Parser ausgeführt zu werden" des Elements. Der Parser übernimmt die Ausführung des Skripts.

Wenn das Element ein src-Attribut hat, kein asynchrones Attribut hat und das Flag "force-async" nicht gesetzt ist, muss das Element am Ende der Liste der Skripte hinzugefügt werden, die so schnell wie möglich ausgeführt werden mit dem Dokument des Skriptelements zum Zeitpunkt der Erstellung eines Skriptalgorithmus gestartet.

Die Aufgabe, die die Quelle der Netzwerkaufgabe nach Abschluss des Abrufalgorithmus in die Aufgabenwarteschlange stellt, muss die folgenden Schritte ausführen:

Wenn das Element jetzt nicht das erste Element in der Liste der Skripte ist, die in der Reihenfolge ausgeführt werden, in der es so schnell wie möglich hinzugefügt wurde, markieren Sie das Element als bereit, brechen Sie diese Schritte jedoch ab, ohne das Skript noch auszuführen.

Ausführung: Führen Sie den Skriptblock aus, der dem ersten Skriptelement in dieser Liste von Skripten entspricht, die so schnell wie möglich in der richtigen Reihenfolge ausgeführt werden.

Entfernen Sie das erste Element aus dieser Liste der Skripte, die so schnell wie möglich ausgeführt werden.

Wenn diese Liste der Skripte, die so schnell wie möglich in der richtigen Reihenfolge ausgeführt werden, noch nicht leer ist und der erste Eintrag bereits als fertig markiert wurde, kehren Sie zum Schritt mit der Bezeichnung Ausführung zurück.

Wenn das Element ein src-Attribut hat Das Element muss zu der Gruppe von Skripten hinzugefügt werden, die zum Zeitpunkt der Erstellung eines Skriptalgorithmus so schnell wie möglich im Dokument des Skriptelements ausgeführt werden.

Die Aufgabe, die die Quelle der Netzwerkaufgabe nach Abschluss des Abrufalgorithmus in die Aufgabenwarteschlange stellt, muss den Skriptblock ausführen und dann das Element aus dem Satz von Skripten entfernen, die so schnell wie möglich ausgeführt werden.

Andernfalls muss der Benutzeragent den Skriptblock sofort ausführen, auch wenn andere Skripte bereits ausgeführt werden.


Was ist mit Javascript-Modul-Skripten type="module"?

Javascript unterstützt jetzt das Laden von Modulen mit folgender Syntax:

<script type="module">
  import {addTextToBody} from './utils.mjs';

  addTextToBody('Modules are pretty cool.');
</script>

Oder mit srcAttribut:

<script type="module" src="http://somedomain.com/somescript.mjs">
</script>

Alle Skripte mit type="module"erhalten automatisch das deferAttribut. Dadurch werden sie parallel (wenn nicht inline) zum anderen Laden der Seite heruntergeladen und anschließend in der angegebenen Reihenfolge ausgeführt, jedoch nachdem der Parser fertig ist.

Modulskripten kann auch das asyncAttribut zugewiesen werden, mit dem Inline-Modulskripte so schnell wie möglich ausgeführt werden. Sie warten nicht, bis der Parser fertig ist, und warten nicht darauf, das asyncSkript in einer bestimmten Reihenfolge im Vergleich zu anderen Skripten auszuführen .

Es gibt ein ziemlich nützliches Zeitdiagramm, das das Abrufen und Ausführen verschiedener Kombinationen von Skripten zeigt, einschließlich Modulskripten hier in diesem Artikel: Laden von Javascript- Modulen .

jfriend00
quelle
Vielen Dank für die Antwort, aber das Problem ist , das Skript wird auf die Seite dynamisch hinzugefügt, die Mittel berücksichtigt wird , async sein . Oder funktioniert das nur in <head>? Und meine Erfahrung ist auch, dass sie in Dokumentreihenfolge ausgeführt werden?
Bergi
@Bergi - Wenn es dynamisch hinzugefügt wird, ist es asynchron und die Ausführungsreihenfolge ist unbestimmt, es sei denn, Sie schreiben Code, um es zu steuern.
jfriend00
Nur, Kolink sagt das Gegenteil ...
Bergi
@Bergi - OK, ich habe meine Antwort dahingehend geändert, dass asynchrone Skripte in einer unbestimmten Reihenfolge geladen werden. Sie können in beliebiger Reihenfolge geladen werden. Wenn ich Sie wäre, würde ich nicht damit rechnen, dass Kolinks Beobachtung so ist, wie sie immer ist. Ich kenne keinen Standard, der besagt, dass ein dynamisch hinzugefügtes Skript sofort ausgeführt werden muss und die Ausführung anderer Skripte blockieren muss, bis es geladen wird. Ich würde erwarten, dass dies browserabhängig ist und möglicherweise auch von Umgebungsfaktoren abhängt (ob das Skript zwischengespeichert ist usw.).
jfriend00
1
@RuudLenders - Das hängt von der Browser-Implementierung ab. Wenn Sie auf das Skript-Tag früher im Dokument stoßen, das jedoch mit gekennzeichnet ist, kann deferder Parser den Download früher starten und gleichzeitig die Ausführung verschieben. Wenn Sie viele Skripte vom selben Host haben, kann ein früherer Start des Downloads das Herunterladen anderer Skripte vom selben Host verlangsamen (da diese um die Bandbreite konkurrieren), auf die Ihre Seite wartet (was nicht der Fall ist defer) Dies könnte ein zweischneidiges Schwert sein.
jfriend00
13

Der Browser führt die Skripte in der Reihenfolge aus, in der er sie findet. Wenn Sie ein externes Skript aufrufen, wird die Seite blockiert, bis das Skript geladen und ausgeführt wurde.

Um diese Tatsache zu testen:

// file: test.php
sleep(10);
die("alert('Done!');");

// HTML file:
<script type="text/javascript" src="test.php"></script>

Dynamisch hinzugefügte Skripte werden ausgeführt, sobald sie an das Dokument angehängt werden.

Um diese Tatsache zu testen:

<!DOCTYPE HTML>
<html>
<head>
    <title>Test</title>
</head>
<body>
    <script type="text/javascript">
        var s = document.createElement('script');
        s.type = "text/javascript";
        s.src = "link.js"; // file contains alert("hello!");
        document.body.appendChild(s);
        alert("appended");
    </script>
    <script type="text/javascript">
        alert("final");
    </script>
</body>
</html>

Die Reihenfolge der Warnungen ist "angehängt" -> "Hallo!" -> "final"

Wenn Sie in einem Skript versuchen, auf ein Element zuzugreifen, das noch nicht erreicht wurde (Beispiel <script>do something with #blah</script><div id="blah"></div>:), wird eine Fehlermeldung angezeigt.

Insgesamt können Sie ja externe Skripte einschließen und dann auf deren Funktionen und Variablen zugreifen, aber nur, wenn Sie das aktuelle <script>Tag beenden und ein neues starten.

Niet the Dark Absol
quelle
Ich kann dieses Verhalten bestätigen. Auf unseren Feedback-Seiten finden sich jedoch Hinweise, dass dies möglicherweise nur funktioniert, wenn test.php zwischengespeichert wird. Kennen Sie hierzu Spezifikations- / Referenzlinks?
Bergi
4
link.js blockiert nicht. Verwenden Sie ein Skript ähnlich Ihrem PHP, um eine lange Downloadzeit zu simulieren.
1983
14
Diese Antwort ist falsch. Es ist nicht immer der Fall, dass "dynamisch hinzugefügte Skripte ausgeführt werden, sobald sie an das Dokument angehängt werden". Manchmal ist dies wahr (z. B. für alte Versionen von Firefox), aber normalerweise nicht. Die Ausführungsreihenfolge, wie in der Antwort von jfriend00 erwähnt, ist nicht bestimmt.
Fabio Beltramini
1
Es ist nicht sinnvoll, dass die Skripte in der Reihenfolge ausgeführt werden, in der sie auf der Seite angezeigt werden, unabhängig davon, ob sie inline sind oder nicht. Warum sollten dann das Google Tag Manager-Snippet und viele andere, die ich gesehen habe, Code zum Einfügen eines neuen Skripts vor allen anderen Skript-Tags auf der Seite haben? Es wäre nicht sinnvoll, dies zu tun, wenn die oben genannten Skripte sicher schon geladen wurden? oder fehlt mir etwas
user3094826
2

Nach dem Testen vieler Optionen habe ich festgestellt, dass die folgende einfache Lösung darin besteht, die dynamisch geladenen Skripte in der Reihenfolge zu laden, in der sie in allen modernen Browsern hinzugefügt werden

loadScripts(sources) {
    sources.forEach(src => {
        var script = document.createElement('script');
        script.src = src;
        script.async = false; //<-- the important part
        document.body.appendChild( script ); //<-- make sure to append to body instead of head 
    });
}

loadScripts(['/scr/script1.js','src/script2.js'])
Flion
quelle