349 Stimmen

Laden und Ausführen von Skripten

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

  • Inline-Code oder von externem URI geladen
  • im <head> oder <body> Tag enthalten [ 1 , 2 ]
  • die keine haben, defer o async Attribut (nur externe Skripte)
  • im statischen Quelltext enthalten oder dynamisch durch andere Skripte hinzugefügt (in verschiedenen Parse-Zuständen, mit verschiedenen Methoden)

Nicht mitgezählt werden Browserskripte von der Festplatte, javascript:URIs und onEvent -Attribute [ 3 Es gibt bereits 16 Alternativen, um JS ausführen zu lassen, und ich bin sicher, dass ich etwas vergessen habe.

Mir geht es nicht so sehr um schnelles (paralleles) Laden, ich bin eher neugierig auf die Ausführungsreihenfolge (die von der Ladereihenfolge abhängen kann und Dokumentenbestellung ). Gibt es eine gute (browserübergreifend) Referenz, die wirklich alle Fälle abdeckt? z.B. http://www.websiteoptimization.com/speed/tweak/defer/ befasst sich nur mit 6 von ihnen und testet hauptsächlich alte Browser.

Da ich befürchte, dass dies nicht der Fall ist, hier meine konkrete Frage: Ich habe einige (externe) Head-Skripte für die Initialisierung und das Laden von Skripten. Dann habe ich zwei statische, Inline-Skripte am Ende des Körpers. Mit dem ersten kann der Skriptlader dynamisch ein weiteres Skriptelement (mit Verweis auf externe Js) an den Body anhängen. Das zweite der statischen Inline-Skripte will js aus dem hinzugefügten, externen Skript verwenden. Kann es sich darauf verlassen, dass das andere ausgeführt wurde (und warum :-)?

447voto

jfriend00 Punkte 632952

Wenn Sie Skripte nicht dynamisch laden oder sie als defer o async dann werden die Skripte in der Reihenfolge geladen, in der sie auf der Seite erscheinen. Dabei spielt es 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 so lange gehalten, bis alle externen Skripte, die vor ihnen kamen, geladen und ausgeführt wurden.

Async-Skripte (unabhängig davon, wie sie als async angegeben sind) werden in einer nicht vorhersehbaren Reihenfolge geladen und ausgeführt. Der Browser lädt sie parallel, und es steht ihm frei, sie in beliebiger Reihenfolge auszuführen.

Es gibt keine vorhersehbare Reihenfolge bei mehreren asynchronen Vorgängen. Wenn man eine vorhersehbare Reihenfolge bräuchte, müsste man sie kodieren, indem man sich für Ladebenachrichtigungen von den asynchronen Skripten registriert und Javascript-Aufrufe manuell aneinanderreiht, wenn die entsprechenden Dinge geladen werden.

Wenn ein Skript-Tag dynamisch eingefügt wird, hängt es vom Browser ab, wie sich die Ausführungsreihenfolge verhält. Sie können sehen, wie sich Firefox verhält in dieser Referenzartikel . Kurz gesagt, die neueren Versionen von Firefox setzen ein dynamisch hinzugefügtes Skript-Tag standardmäßig auf async, sofern das Skript-Tag nicht anders festgelegt wurde.

Ein Skript-Tag mit async kann ausgeführt werden, sobald es geladen ist. Der Browser kann den Parser sogar anhalten und das Skript ausführen, sobald er etwas anderes tut. Es kann also wirklich fast jederzeit ausgeführt werden. Wenn das Skript im Cache gespeichert wurde, kann es fast sofort ausgeführt werden. Wenn das Skript eine Weile braucht, um zu laden, kann es ausgeführt werden, nachdem der Parser fertig ist. Das Einzige, was man sich bei async ist, dass sie jederzeit ablaufen kann und dass die Zeit nicht vorhersehbar ist.

Ein Skript-Tag mit defer wartet, bis der gesamte Parser fertig ist und führt dann alle Skripte aus, die mit defer in der Reihenfolge, in der sie angetroffen wurden. So können Sie mehrere Skripte, die voneinander abhängen, als defer . Sie werden alle verschoben, bis der Dokumentenparser fertig ist, aber sie werden in der Reihenfolge ausgeführt, in der sie gefunden wurden, wobei ihre Abhängigkeiten erhalten bleiben. Ich denke an defer wie die Skripte in eine Warteschlange gestellt werden, die verarbeitet wird, nachdem der Parser fertig ist. Technisch gesehen kann der Browser die Skripte jederzeit im Hintergrund herunterladen, aber sie werden nicht ausgeführt oder blockieren den Parser, bis der Parser mit dem Parsen der Seite und dem Parsen und Ausführen aller Inline-Skripte fertig ist, die nicht als defer o async .

Hier ist ein Zitat aus diesem Artikel:

Skripte, die in ein Skript eingefügt werden, werden in IE und WebKit asynchron ausgeführt, aber synchron in Opera und Firefox vor Version 4.0.

Der relevante Teil der HTML5-Spezifikation (für neuere konforme Browser) lautet aquí . Darin wird viel über asynchrones Verhalten geschrieben. Offensichtlich gilt diese Spezifikation nicht für ältere Browser (oder nicht konforme Browser), deren Verhalten Sie wahrscheinlich testen müssen, um es zu bestimmen.

Ein Zitat aus der HTML5-Spezifikation:

Dann ist die erste der folgenden Optionen zu wählen, die die Situation beschreibt befolgt werden:

Wenn das Element ein src-Attribut hat, und das Element ein defer Attribut hat, und das Element als "Parser-inserted" gekennzeichnet wurde, und das Element kein async-Attribut hat T am Ende der Liste der Skripte hinzugefügt werden, die ausgeführt werden, wenn das Dokument ausgeführt wird, das mit dem Dokument des Parsers verbunden ist, der das Element erstellt hat.

Die Aufgabe, die die Quelle der Netzwerkaufgabe auf die der Abrufalgorithmus abgeschlossen ist, muss das Element auf "bereit zur Parser-Ausführung" setzen. Der Parser wird die Ausführung des Skripts übernehmen.

Wenn das Element ein src-Attribut hat, und das Element als "Parser-inserted" gekennzeichnet wurde und das Element kein async-Attribut hat Das Element ist das anhängige, das Parsing blockierende Skript des Dokuments von des Parsers, der das Element erstellt hat. (Es kann nur ein solches Skript pro Dokument geben.)

Die Aufgabe, die von der Quelle der Netzwerkaufgabe auf die Taskleiste gelegt wird der Abrufalgorithmus abgeschlossen ist, muss das Element auf "bereit zu Parser-Ausführung" setzen. Der Parser wird die Ausführung des Skripts übernehmen.

Wenn das Element kein src-Attribut hat und das Element als "Parser-inserted" gekennzeichnet ist und das Dokument des HTML-Parsers oder XML-Parser, der das Skriptelement erstellt hat, ein Style Sheet hat, das Skripte blockiert Das Element ist das ausstehende Parsing des Dokuments des Parsers, der das Element erstellt hat. (Es kann nur ein solches Skript pro Dokument geben.)

Setzt das Flag "bereit für die Parser-Ausführung" des Elements. Der Parser wird die Ausführung des Skripts übernehmen.

Wenn das Element ein src-Attribut hat, kein async-Attribut hat, und das Flag "force-async" nicht gesetzt ist T am Ende der Liste der Skripte hinzugefügt werden, die so schnell wie möglich nacheinander ausgeführt werden wie möglich in Verbindung mit dem Dokument des Skriptelements zum Zeitpunkt der zum Zeitpunkt des Starts des Algorithmus zur Vorbereitung eines Skripts.

Die Aufgabe, die die Quelle der Netzwerkaufgabe einmal in die Aufgabenwarteschlange stellt Die Aufgabe, die die Netzwerk-Task-Quelle in die Warteschlange stellt, nachdem der Abrufalgorithmus abgeschlossen ist, muss die folgenden Schritte ausführen:

Wenn das Element jetzt nicht das erste Element in der Liste ist das so schnell wie möglich in der Reihenfolge ausgeführt wird, in der es hinzugefügt wurde oben, dann markieren Sie das Element als bereit b ohne das Skript noch auszuführen.

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

Entfernen Sie das erste Element aus dieser Liste von Skripten, die ausgeführt werden sollen so schnell wie möglich ausführen.

Wenn diese Liste von Skripten, die in der Reihenfolge so bald wie möglich ausgeführt wird noch nicht leer ist und der erste Eintrag bereits als bereit, dann springen Sie zurück zum Schritt mit der Bezeichnung Ausführung.

Wenn das Element ein src-Attribut hat Das Element muss angezeigt werden Menge von Skripten hinzugefügt werden, die so bald wie möglich nach dem Dokument des Skriptelements zu dem Zeitpunkt, zu dem der Algorithmus zur Vorbereitung eines Skripts gestartet wird.

Die Aufgabe, die die Quelle der Netzwerkaufgabe einmal in die Aufgabenwarteschlange stellt der Abrufalgorithmus die dann das Element aus der Menge der Skripts entfernen, die ausgeführt werden, sobald so schnell wie möglich ausführen.

Ansonsten Der User-Agent muss sofort die auch wenn bereits andere Skripte ausgeführt werden.


Wie sieht es mit Javascript-Modulskripten aus? type="module" ?

Javascript hat jetzt Unterstützung für das Laden von Modulen mit einer Syntax wie dieser:

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

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

Oder, mit src Attribut:

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

Alle Skripte mit type="module" werden automatisch mit dem defer Attribut. Dadurch werden sie parallel (wenn nicht inline) zu anderen Ladevorgängen der Seite heruntergeladen und dann der Reihe nach ausgeführt, aber erst nachdem der Parser fertig ist.

Modulskripte können auch mit der async Attribut, das Inline-Modulskripte so schnell wie möglich ausführt, ohne zu warten, bis der Parser fertig ist und ohne zu warten, bis die async Skript in einer bestimmten Reihenfolge im Verhältnis zu anderen Skripten.

Es gibt ein sehr nützliches Zeitdiagramm, das den Abruf und die Ausführung verschiedener Kombinationen von Skripten, einschließlich Modulskripten, hier in diesem Artikel zeigt: Laden von Javascript-Modulen .

42voto

Dehan Punkte 4028

Eine gute Zusammenfassung von @addyosmani

enter image description here

Schamlos kopiert von https://addyosmani.com/blog/script-priorities/

12voto

Flion Punkte 9646

Nach dem Testen vieler Optionen habe ich festgestellt, dass die folgende einfache Lösung die dynamisch geladenen Skripte in der Reihenfolge lädt, 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'])

1voto

Panu Logic Punkte 1739

Ich hatte Schwierigkeiten zu verstehen, wie man ein eingebettetes Modul-Skript ausführt, bevor das Onload-Ereignis eintritt. Die obigen Antworten haben mir sehr geholfen, aber lassen Sie mich eine Teilantwort hinzufügen, die mein spezielles Problem des Missverständnisses der "Lade- und Ausführungsreihenfolge von Skripten" behebt.

Ich habe zuerst ... verwendet, was zu dem seltsamen Problem führte, dass es funktionierte, wenn die Seite normal geladen wurde, aber nicht, wenn sie im Debugger auf FireFox lief. Das machte die Fehlersuche sehr schwierig.

Hinweis: Skripte, deren Typ "Modul" ist, haben immer ein implizites "deferred"-Attribut, was bedeutet, dass sie das Parsen von HTML nicht stoppen, was bedeutet, dass das Onload-Ereignis eintreten kann, bevor das Skript ausgeführt wird. Das wollte ich nicht. Aber ich wollte type="module" verwenden, um meine nicht exportierten JavaScript-Funktionen und -Variablen für andere Skripte auf derselben Seite unsichtbar zu machen.

Ich habe verschiedene Optionen ausprobiert, aber dank der obigen Antworten bin ich zu der Erkenntnis gelangt, dass das Hinzufügen des async-Attributs zu einem Skript vom Typ Modul bedeutet, dass das Skript asynchron geladen wird, aber sobald es geladen ist, wird es sofort ausgeführt.

In meinem Fall handelte es sich jedoch um ein in eine HTML-Seite eingebettetes Skript. DARUM bedeutete es, dass nichts "asynchron" geladen werden musste. Es wurde bereits mit der Seite geladen, da es in sie eingebettet war. Daher wurde es mit dieser Änderung sofort ausgeführt - was ich ja auch wollte.

Ich denke, es lohnt sich, auf diesen speziellen Fall hinzuweisen, weil er etwas kontra-intuitiv ist: Um ein eingebettetes Skript SOFORT ausführen zu lassen, müssen Sie das Attribut ASYNC zu seinem Tag hinzufügen.

Normalerweise könnte man denken, dass "async" bedeutet, dass etwas asynchron, in unbestimmter Reihenfolge und nicht sofort geschieht. Aber man muss sich darüber im Klaren sein, dass "async" asynchrones LADEN, aber sofortige AUSFÜHRUNG nach dem Laden bedeutet. Und wenn das Skript eingebettet ist, muss es nicht geladen werden und wird daher sofort ausgeführt.

Zusammenfassung: Verwendung

     <script type="module" async> ... </script>

um ein in eine HTML-Seite eingebettetes Modul-Skript sofort zur Ausführung zu bringen.

-1voto

Perfekte Übereinstimmung für Ihre Anfrage!

Wenn keine der Lösungen für Sie funktioniert hat, dann lesen Sie bitte die untenstehende Lösung von mir, die ich von meiner Seite aus entwickelt habe.

Ich war auch auf der Suche nach der Lösung, aber nach der Suche eine Menge, habe ich meinen Code wie unten, die perfekt funktioniert für mich zusammengefasst!!!

Dies ist nützlich, wenn Sie eine Funktionalität wünschen, die, nachdem das vorherige Skript vollständig geladen wurde, nur das nächste Skript lädt!

Erstellen Sie einfach eine Datei mit dem Namen jsLoadScripts.js und fügen Sie sie in den Kopf oder am Ende des Textkörpers ein.

//From Shree Aum Software Solutions
//aumsoftwaresolutions@gmail.com

//script incrementor for array
scriptIncrementor = 0;

//define your script urls here
let scripts = [
    "https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js",
    "jsGlobalFunctions.js",
    "jsDateParser.js",
    "jsErrorLogger.js",
    "jsGlobalVariables.js",
    "jsAjaxCalls.js",
    "jsFieldsValidator.js",
    "jsTableClickEvent.js",
    "index.js",
    "jsOnDocumentReady.js",
];

//it starts with the first script and then adds event listener to it. which will load another script on load of it. then this chain goes on and on by adding dynamic event listeners to the next scripts! 
function initializeScripts() {
    var script = document.createElement("script");
    script.type = "text/javascript";
    script.src = scripts[scriptIncrementor];
    document.head.appendChild(script);
    script.addEventListener("load", function () {
        loadNextScript();
        scriptIncrementor++;
    });
}

// this function adds event listener to the scripts passed to it and does not allow next script load until previous one has been loaded!
function loadNextScript() {
    if (scriptIncrementor != scripts.length - 1) {
        var script = document.createElement("script");
        script.type = "text/javascript";
        script.src = scripts[scriptIncrementor + 1];
        document.head.appendChild(script);
        script.addEventListener("load", function () {
            loadNextScript();
            scriptIncrementor++;
        });
    }
}

// start fetching your scripts
window.onload = function () {
    initializeScripts();
};

Dies kann einige geschwindigkeitsbezogene Probleme verursachen, daher können Sie die Funktion initializeScripts() mit Ihren individuellen Bedürfnissen!

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X