Javascript defer und async – einbinden und ausführen

Javascript mit script-Tag einbinden

Javascript wurde viele Jahre bevorzugt ans Ende der HTML-Seite vor das schließende body-Tag gesetzt um zu verhindern, dass Laden und Ausführen des Scripts das Parsen des Dokuments blockiert. Heute haben wir die Attribute defer und async für das script-Tag, um den Ladeprozess besser zu steuern.

23-02-02 SITEMAP CSS HTML JS Basis JS Web Tutorial SVG

Ablauf

Der Browser interpretiert das HTML-Dokument Zeile für Zeile. Sitzt das script-Tag im head-Element der Seite, wird der Browser das Javascript an Ort und Stelle ausführen.

Viele Aktionen können allerdings erst ausgeführt werden, wenn bestimmte Elemente der Webseite geladen sind. Der Browser muss die angesprochenen Elemente bereits geladen haben, sonst entsteht ein Javascript-Fehler.


<head>
   <title>Wohin mit dem script-Tag?</title>
   <link media="screen" href="style.css">
   <script>
      const header = querySelector ('header'); <-- Fehlermeldung
   </script>                                        │
</head>                                             │
<body>                                              │
   <header>             existiert erst hier ◀───────┘
       …
   </header>
</body>
</html>

Die Ausführung des Scripts muss mit onload oder addEventListener unterbrochen und zurückgestellt werden, bis das Element im DOM geladen ist.


<head>
   <script>
      window.addEventListener ('load', function () {
         let header = querySelector ('header');
      });
   </script>
</head>
<body>
   <header>
       …
   </header>
</body>

Die Logik für das Anhalten des Scripts und das Registrieren des Events, bei dem das Script fortgesetzt werden soll, ist kompliziert.

Script im head-Element des Dokuments laden
Laden und Ausführen des Scripts blockieren das Parsen des Dokuments

Script am Ende der Seite

Lange Zeit war die einfachste Lösung, das Script am Ende der HTML-Seite vor dem schließenden body-Tag unterzubringen. An diesem Punkt ist sicher gestellt, dass alle Elemente des Dokuments geladen sind. Die generelle Regel lautete:

CSS-Dateien im head, Javascript am Ende der Webseite einbinden.


<html lang="de">
<head>
   …
</head>
<body>
   <div id="header"> … </div>
   
   <script src="/jquery.min.js"></script>
   <script>
      alert ("Hallo World!");
      document.getElementById("header");
   </script>
</body>
</html>

Script vor dem schließenden body-Tag laden
»Eliminate render-blocking JavaScript and CSS in above-the-fold» – das beschleunigt die Anzeige der Seite im Browser, ist aber auch ein Spagat zwischen Programmlogik und der Platzierung des Scripts.

Script asynchron laden

Mit async lädt der Browser das Script parallel zu anderen Ressourcen und beginnt derweil mit dem Aufbau der Seite. Sobald das Script geladen ist, entsteht eine Pause, weil der Browser das Script erst interpretieren muss.

<script src="app.js" async></script>

Vorzugsweise sollten externe Scripts asynchron geladen werden. Für Third Party-Scripte ist async ein Muss. Wenn deren Server gerade in die Knie geht, zieht das Script die eigene Seite mit in den Abgrund. Das asynchrone Laden von externen Scripten kürzt die Ladezeit und das Script kann sofort ausgeführt werden.

async kann aber auch ein Dilemma sein, denn es ist nicht vorhersehbar, wann das Script tatsächlich geladen und ausführbar ist.

Script im head-Element mit async laden

Laden mit defer verzögern

Das defer-Attribut im script-Tag verspricht dem Browser, dass die Webseite nicht durch Anweisungen wie document.write (was sowieso unerwünscht ist) geändert wird.

<script src="app.js" defer></script>

Der Browser lädt das Script im Hintergrund, aber verschiebt die Ausführung, bis alle anderen Komponenten geladen und die Seite geparst ist.

Script im Head-Element mit defer-Attribut laden
Scripte mit defer-Attribut blockieren das Parsen der Seite nicht, werden aber auch erst nach dem Parsen ausgeführt.
<script src="script1.js"></script>
<script src="script2.js"></script>

script1.js lädt ein Bild, script2.js wendet einen Filter auf das Bild an. Sitzen die Skript-Tags im Kopf der Seite, erzeugen sie beiden Fehler: script1.js kennt das HTML-Element für die Platzierung des Bildes nicht, script2.js kann den Filter nicht auf das Bild anwenden.

<script src="script1.js" defer></script>
<script src="script2.js"></script>

Mit defer wird das Bild zwar geladen und eingesetzt, aber dieser Prozess dauert an, während der Browser script2.js schon ausführt. Zum Zeitpunkt der Ausführung von script2.js ist das Bild noch nicht geladen. Dasselbe Situation tritt ein, wenn script1.js als async markiert wäre.

<script src="script1.js" defer></script>
<script src="script2.js" defer></script>

Der Browser lädt Skripte mit einem defer-Attribut in der Reihenfolge, in der sie im Code erscheinen. Dabei wird der Browser nicht blockiert. Die Skripte werden aber erst ausgeführt, wenn das Dokument vollständig geladen wurde.

Skripte mit einem async-Attribut werden sofort – asynchron – ausgeführt, während der Browser dabei nicht blockiert wird. Das kann dazu führen, das script2.js vor script1.js geladen und ausgeführt wird. Wenn es nicht auf die Reihenfolge beim Laden der Skripte ankommt, wäre async die praktikable Lösung.

DOMContentLoaded mit defer / async

Sowohl async als auch defer weisen den Browser an, die Seite zu laden und aufzubauen, und die Scripte im Hintergrund zu laden. Die Scripte sollen den Aufbau des DOM und das Rendern der Seite nicht blockieren.

asyncdefer
Reihenfolge Mit async führt der Browser Scripte in der Reihenfolge »zuerst geladen zuerst ausgeführt« aus. Das zuerst geladene Script läuft zuerst, egal an welcher Stelle das Script im DOM erscheint. Mit defer führt der Browser Skripte in der Reihenfolge aus, in der sie aufgeführt sind.
DOMContentLoaded Skripte mit async können geladen und ausgeführt werden während das Dokument noch nicht vollständig geladen wurde, etwa wenn das Script klein oder im Cache ist und das Dokument lang genug. Skripte mit defer müssen warten, bis das Dokument geladen und geparst ist, also bis DOMContentLoaded.

Script Insertion

Wenn Javascript nicht für den Aufbau der Seite benötigt wird, muss ein externes Script erst geladen werden, wenn das DOM und alle Elemente geladen sind. Dafür sorgen ein paar Zeilen Javascript am Ende der Seite.

Javascript nachladen
function loadScriptAfter() {
   let script = document.createElement("script");
   script.src = "script.js";
   document.body.appendChild(script);
}

if (window.addEventListener) {
   window.addEventListener("load", loadScriptAfter);
} else if (window.attachEvent) {
   window.attachEvent("onload", loadScriptAfter);
} else {
   window.onload = loadScriptAfter;
}

Mit async ist Script Insertion im Grunde genommen überflüssig geworden. Das ist mal ein Wort!

Ladezeiten verkürzen

Das Auslagern von Javascript in eine externe Scriptdatei verbessert die Performance der Webseite. Wenn die Scripte auf mehreren Webseiten eingesetzt werden, liegen die Scripte beim Aufrufe einer weiteren Seite bereits im Cache des Browsers.

Wenn Design und Entwicklung der Webseite abgeschlossen sind, wurden früher externe Javascript-Dateien so weit wie möglich zu einer Script-Datei zusammengeführt. Weniger HTTP-Requests – das reduziert die Ladezeit.

Mit HTTP/2 fällt diese Optimierung weniger ins Gewicht, zudem hat ECMAScript 6 den Export und Import von Script-Dateien als Module mitgebracht.