JavaScript: Einbindung in HTML mit dem script-Element

Vorbemerkung

Es gibt viele Möglichkeiten, JavaScript in HTML-Dokumente einzubetten. In dieser Einführung werden nur wenige gängige vorgestellt und empfohlen. Dieses Kapitel geht davon aus, dass HTML und JavaScript möglichst getrennt werden und sich JavaScripte eigenständig hinzuschalten. Die Hintergründe zu diesem Ansatz finden Sie im Kapitel Sinnvoller JavaScript-Einsatz.

Das script-Element

Zur Einbindung von JavaScript-Code in ein HTML-Dokument existiert das HTML-Element script. Dieses darf sowohl im Kopf (head-Element) als auch im Körper (body-Element) eines HTML-Dokuments auftauchen. Es kann entweder direkt JavaScript-Code beinhalten, wie in diesem Beispiel:

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>Dokument mit integriertem JavaScript</title>
</head>
<body>
<h1>Dokument mit integriertem JavaScript</h1>
<script>
window.alert("Hallo Welt!");
</script>
</body>
</html>

Oder es kann leer sein und auf eine externe Datei mit JavaScript-Code verweisen. Diese Nutzungsweise sollten Sie vorziehen und Ihre JavaScripte möglichst in separate Dateien auslagern.

Schreiben Sie dazu Ihren JavaScript-Code in eine eigene Datei und speichern Sie sie mit der Dateiendung .js ab. Notieren Sie am Dokument-Ende ein script-Element, das den Browser auf die externe JavaScript-Datei hinweist. Dazu notieren Sie im src-Attribut die Adresse (URL), unter der das Script abrufbar ist.

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>Dokument mit externem JavaScript</title>
</head>
<body>
<h1>Dokument mit externem JavaScript</h1>
<script src="script.js"></script>
</body>
</html>

In der Datei script.js können Sie nun JavaScript-Anweisungen, Funktionen, Objekte und so weiter notieren. Schreiben Sie zum Test einmal folgende Anweisung in die JavaScript-Datei:

window.alert('Hallo Welt!');

Sie können mit dieser Methode auch mehrere Scripte einbinden. Platzieren Sie dazu mehrere script-Elemente untereinander.

Beachten Sie, dass diese eingebundenen Dateien direkt JavaScript-Code enthalten müssen. HTML-Code darf darin nicht vorkommen – in JavaScript-Strings ist er natürlich noch erlaubt. Insbesondere ist es nicht nötig, den JavaScript-Code in der separaten Datei noch einmal in ein script-Element zu verpacken. Dies würde dazu führen, dass der Browser den JavaScript-Code nicht korrekt ausführt.

Ihre Script-Dateien können Sie – genauso wie Stylesheets, Grafiken usw. – auch in Unterverzeichnissen und sogar auf anderen Webservern unterbringen. Solange die angegebene URL korrekt ist, wird ein JavaScript-fähiger Browser sie beim Anzeigen des Dokuments herunterladen und ausführen.

Ausführung von script-Elementen

Mit dem script-Element können Sie sowohl Scripte im Dokumentkopf <head>…</head> als auch im Dokumentkörper <body>…</body> einbetten. Die Ausführung des Scriptcodes läuft nach gewissen Regeln ab, die wir im Folgenden betrachten.

Sobald der Browser das HTML-Dokument vom Webserver empfängt, beginnt er damit, den Quelltext zu verarbeiten und in eine interne Speicherstruktur, das Document Object Model (DOM) zu überführen. Das dafür zuständige Browser-Modul nennt sich Parser und der Verarbeitungsvorgang Parsen.

Sobald der Parser auf ein script-Element trifft, wird das Parsen des HTML-Dokuments angehalten und der JavaScript-Code innerhalb des script-Elements ausgeführt. Das gilt auch für externe JavaScript-Dateien: Der HTML-Parser stoppt, lädt die externe JavaScript-Datei vom Webserver, führt den JavaScript-Code aus und fährt erst dann mit der Verarbeitung des restlichen HTML-Quelltextes fort.

Diese Vorgehensweise, den JavaScript-Code direkt beim Einlesen des HTML-Dokuments auszuführen, hat verschiedene Konsequenzen.

Scripte haben Zugriff auf die Objekte vorher eingebundener Scripte

Der JavaScript-Code in script-Elementen bzw. der externe Script-Code wird in der Reihenfolge ausgeführt, in der die script-Elemente im HTML-Quelltext notiert sind. Wenn Sie verschiedene Scripte haben, die aufeinander aufbauen, so müssen Sie sie nacheinander einbinden.

<script src="grundlagenscript.js"></script>
<script src="aufbauscript.js"></script>
<script>
// Anwendung der Scripte
helferfunktion();
</script>

Das Beispiel bindet drei Scripte ein, die ersten beiden als externe Dateien, das dritte direkt im HTML-Code. Der Browser führt die Scripte in der Reihenfolge ihrer Einbindung aus. Daher können spätere Scripte die Objekte, Funktionen und Variablen nutzen, die die vorher eingebundenen Scripte definiert haben.

Im Beispiel wird zuerst grundlagenscript.js eingebunden, heruntergeladen und ausgeführt. Das darauffolgende Script aus der Datei aufbauscript.js kann die darin notierten Funktionen nutzen. Schließlich kann das dritte Script eine Funktion nutzen, die in aufbauscript.js definiert wurde.

Externe Scripte verzögern den Aufbau des Dokuments

Dass der Browser die eingebundenen Scripte nicht erst nach, sondern bereits während des Einlesens des HTML-Codes ausführt, hat Vor- und Nachteile.

Einerseits werden Scripte so schnell wie möglich ausgeführt und es ist garantiert, dass ein externes Script ausgeführt wird, bevor ein nachfolgendes internes Script abgearbeitet wird.

Andererseits verlangsamt sich der Seitenaufbau, wenn große externe Scriptdateien vom Webserver heruntergeladen werden.

Es ist daher empfehlenswert, alle script-Elemente in der notwendigen Reihenfolge am Dokument-Ende zu platzieren. Dadurch wird der Aufbau des Dokumentes nicht durch das Herunterladen und Ausführen von JavaScript unterbrochen.

Scripte können während des Ladens das Dokument mit document.write ergänzen

Mit der Methode document.write kann ein Script schon während des Ladens das Dokument direkt beeinflussen und einige Weichen stellen. document.write nimmt HTML-Code als String entgegen. Es fügt den HTML-Code an der Stelle ins Dokument ein, an der das zugehörige script-Element steht.

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>Zugriff auf das DOM während dem Parsen des Dokuments</title>
<script>
document.write("<link rel='stylesheet' href='stylesheet.css'>");
</script>
</head>
<body>
<script>
document.write("<p><a href='javascript:location.reload()'>" +
  "Seite mittels JavaScript neu laden<\/a><\/p>");
</script>
</body>
</html>

Das Beispiel enthält zwei Scripte mit document.write-Aufrufen. Diese schreiben HTML-Elemente ins Dokument, einmal ein Verweis auf ein Stylesheet und einmal ein Textabsatz mit einem JavaScript-Link.

document.write ist beim Unobtrusive JavaScript nur selten sinnvoll. Es gibt zwar Inhalte, die nur bei verfügbarem JavaScript sichtbar sein sollen, da sie auf JavaScript-Funktionalität beruhen. Im Beispiel ist das ein Link, der die Seite mittels JavaScript neu lädt. Allerdings sollten Sie solche Inhalte ohne document.write dynamisch ins Dokument einfügen. Die dazu nötigen Techniken werden wir noch kennenlernen.

document.write hat zwei möglichen Anwendungsfälle, von denen wir einen kennenlernt haben: Das Ergänzen eines Dokuments während der Browser den HTML-Code einliest. Wenn document.write jedoch nach dem Einlesen des HTML-Codes aufgerufen wird, hat die Methode einen ganz anderen Effekt: Sie überschreibt den gesamten Inhalt des Dokuments. Sie eignet sich also nicht dazu, das vorhandene Dokument gezielt via JavaScript zu ändern.

Ein Script hat Zugriff auf die Elemente vor dem zugehörigen script-Element

Die häufigste Aufgabe von JavaScripten ist der Zugriff auf das Dokument über die DOM-Schnittstelle. Diese macht die Elemente und deren Textinhalte als Knotenobjekte zugänglich.

Ein Script wird wie gesagt während des Einlesens des HTML-Dokuments ausgeführt. Zu diesem Zeitpunkt hat es noch keinen Zugriff auf den gesamten DOM-Elementbaum. Stattdessen kann es nur auf einen Teil-Baum zugreifen, nämlich auf die Elemente, die vor dem zugehörigen script-Element liegen und somit bereits eingelesen wurden.

<!DOCTYPE html>
<html lang="de">
<head>
<title>Zugriff auf das DOM während dem Parsen des Dokuments</title>
<script>
// Der Titel ist an dieser Stelle bereits verfügbar:
window.alert( document.title );
// Der Dokumentkörper allerdings noch nicht (ergibt null):
window.alert( document.body );
// Die Überschrift ebensowenig (ergibt null):
window.alert( document.getElementById("überschrift") );
</script>
</head>
<body>

<script>
window.alert( document.title ); // OK
// Der Dokumentkörper ist erst an dieser Stelle verfügbar:
window.alert( document.body );
// Die Überschrift allerdings noch nicht (ergibt null):
window.alert( document.getElementById("überschrift") );
</script>

<h1 id="überschrift">Beispielüberschrift</h1>

<script>
window.alert( document.title ); // OK
window.alert( document.body ); // OK
// Die Überschrift ist erst an dieser Stelle verfügbar:
window.alert( document.getElementById("überschrift") );
</script>

</body>
</html>

Das Beispiel enthält drei Scripte, die jeweils versuchen, auf den Dokument-Titel (title-Element), den Dokument-Körper (body-Element) und eine Überschrift (h1-Element) zuzugreifen. Je nachdem, an welcher Stelle sich das Script und das angesprochene Element befinden, ist der Zugriff auf das Element möglich oder nicht.

Der Zugriff funktioniert erst dann, wenn das gesuchte Element dem jeweiligen Script vorangeht und bereits eingelesen wurde. Das Element muss dazu noch nicht abgeschlossen sein. Im Beispiel kann ein Script im body-Element bereits auf das geöffnete, aber noch nicht geschlossene body-Element zugreifen. Das Script hat zudem Zugriff auf die vorherigen Geschwisterelemente (im Beispiel das h1-Element), nicht jedoch auf die folgenden.

Vielleicht verstehen Sie nun, warum wir externe Scripte ans Dokument-Ende setzen haben: Sie haben dadurch Zugriff auf das gesamte Dokument.

Scripte im Hintergrund laden

Wie beschrieben werden Scripte beim Einlesen des Dokuments ausgeführt. Damit ist die Reihenfolge der Ausführung dieselbe wie die Reihenfolge der Scripte im Dokument. Der große Nachteil ist, dass das Herunterladen und Ausführen der Scripte den Aufbau der Seite verlangsamt.

Um die Ladegeschwindigkeit zu verbessern, haben wir einen Trick kennengelernt: Scripte werden ausgelagert und die script-Elemente ans Dokument-Ende verschoben. Dadurch verlangsamen sie die Verarbeitung des restlichen Dokumentinhalts nicht.

Dieser Trick hat allerdings den Nachteil, dass der Browser die Scripte erst bemerkt, wenn das HTML-Dokument fast komplett heruntergeladen und eingelesen ist. Erst spät startet der Browser das Herunterladen und Ausführen der Scripte. Diese Verzögerung kann zu unerwünschten Effekten führen.

Beim Unobtrusive JavaScript ist es üblich, dass Scripte das Dokument verändern und Funktionen hinzufügen. Passiert das einige Sekunden nach dem erstmaligen Anzeigen der Seite, so verändert sich die Seite plötzlich, während der Benutzer die Seite bereits liest.

HTML ermöglicht es daher, ein Script dem Browser so früh wie möglich bekannt zu machen, es aber erst später auszuführen. Dadurch kann der Browser das Script bereits im Hintergrund herunterladen. Das Einlesen des Dokuments wird aber nicht verzögert.

Das Herunterladen des Scripts und das Einlesen des Dokuments laufen somit gleichzeitig nebeneinander. Das Script blockiert die Verarbeitung des Dokuments nicht. Der Browser wartet nicht auf das Herunterladen und Ausführen des Scripts.

Dazu gibt es zwei zusätzliche HTML-Attribute, die Sie dem script-Element geben können: defer und async.

defer: Script ausführen, sobald das Dokument geladen ist

defer ist ein Attribut ohne Wert. Sie notieren einfach defer in der Attributliste im Start-Tag:

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

Wenn das defer-Attribute gesetzt ist, so lädt der Browser das Script sobald möglich herunter. Das Einlesen und der Aufbau des Dokuments läuft derweil weiter. Der Browser führt das Script nämlich erst aus, sobald das Dokument fertig geladen ist.

Das englische Wort defer bedeutet »verzögern«. Der Browser verzögert die Script-Ausführung bis zum erfolgreichen Übertragen und Einlesen des HTML-Codes.

Damit hat das Script garantierten Zugriff auf den gesamten DOM-Elementbaum. Es verhält sich insofern wie ein Script, das am Ende des Dokuments eingebunden ist. Mit dem Unterschied, dass der Browser früh mit dem Herunterladen beginnen kann. Dies kann den Seitenaufbau enorm beschleunigen.

Die Position eines solchen Scripts im Dokument hat keinen Einfluss auf den Zeitpunkt seiner Ausführung. Der Zeitpunkt ist garantiert, nämlich nach dem Einlesen des Dokuments.

Damit der Browser das Script möglichst früh im Hintergrund herunterlädt, sollten Sie das zugehörige Script-Element im head notieren:

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>Dokument mit defer-Script</title>
<script src="script.js" defer></script>
</head>
<body>
<h1>Dokument mit defer-Script</h1>
</body>
</html>

Das defer-Attribut funktioniert nur bei externen, ausgelagerten Scripten. Wenn Sie es angeben, müssen Sie auch ein src-Attribut angeben. defer funktioniert nicht bei direkt ins Dokument integriertem JavaScript-Code.

Wenn es mehrere Scripte mit defer im Dokument gibt, so werden sie garantiert in der Reihenfolge ausgeführt, in der sie im Dokument notiert sind:

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

Im obigen Beispiel lädt der Browser Script 1 und 2 parallel herunter. Beide werden ausgeführt, wenn das Dokument fertig eingelesen, und zwar erst Script 1 und dann Script 2.

Ein Script, das Sie mit defer laden, darf nicht document.write verwenden. Wenn Sie das Dokument ergänzen möchten, so müssen Sie stattdessen die DOM-Schnittstelle verwenden.

async: Script ausführen, sobald sie heruntergeladen sind

Das zweite Attribut, async, ist auch ein Attribut ohne Wert:

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

async bewirkt ebenfalls, dass der Browser das Script sobald möglich herunterlädt. Das Einlesen des Dokuments wird nicht unterbrochen.

Der Unterschied zu defer ist nun folgender: Das Script mit async wird ausgeführt, sobald es fertig heruntergeladen ist. Der Browser wartet mit der Ausführung nicht, bis das Dokument vollständig eingelesen ist.

async steht für das englische asynchronous, was hier asynchron, zeitlich losgelöst und entkoppelt bedeutet. Die Script-Ausführung ist nämlich losgelöst vom Dokumentaufbau.

Das Script wird schnellstmöglich ausgeführt. Es hat auf die Elemente Zugriff, die zu diesem Zeitpunkt bereits eingelesen wurden. Welche das sind, ist willkürlich und kann sich von Mal zu Mal unterscheiden.

Sie sollten in einem solchen Script nicht davon ausgehen, dass bestimmte Elemente bereits eingelesen sind und ein DOM-Zugriff möglich ist. async eignet sich daher nur für besondere Scripte, die nicht zielgenau über die DOM-Schnittstelle auf das Dokument zugreifen.

Ein Beispiel sind Scripte zur Besucherzählung. Diese erheben Daten und schicken sie gegebenenfalls an den Server, aber benötigen keinen Zugriff auf die Inhalte im Dokument-Körper (body-Element).

In den meisten Fällen sollten Sie Ihre script-Elemente ans Dokumentende stellen – ohne defer und async – oder im Dokument-Kopf mit defer laden.

Das noscript-Element

Das noscript-Element ist als Gegenstück zu script gedacht: Damit lassen sich Alternativinhalte für Programme ins Dokument einfügen, die keine Scripte unterstützen. Browser, in denen JavaScripte deaktiviert oder nicht verfügbar ist, zeigen den Alternativinhalt an. Der Inhalt richtet sich auch an Programme wie Suchmaschinen-Robots, die das Dokument automatisiert verarbeiten, ohne die Scripte dabei zu beachten.

<noscript>
   <p>Dieser Absatz ist gehört zum Inhalt des Dokuments, ist aber
   im Browser nur zu sehen, wenn JavaScript deaktiviert oder nicht
   zur Verfügung steht.</p>
</noscript>

Der Sinn von noscript ist, die Informationen zugänglich zu machen, die sonst nur mithilfe des Scriptes zugänglich wären oder sogar durch das Script eingefügt werden. Diese Abwärtskompatibilität einer Website und die Zugänglichkeit aller Inhalte ohne JavaScript ist zwar erstrebenswert. Allerdings zäumt diese Vorgehensweise das Pferd von hinten auf.

Unobtrusive JavaScript empfiehlt eine andere Vorgehensweise: Alle Informationen liegen bereits im Dokument. Es ist daher ohne JavaScript gut bedienbar. Mittels JavaScript werden Zusatzfunktionen eingebaut, die die Bedienung und das Lesen der Inhalte vereinfachen und verbessern.

Im Unobtrusive JavaScript kommt dem noscript-Element daher keine Bedeutung zu. Von seiner Verwendung wird sogar abgeraten. Denn es verleitet dazu, JavaScript-Logik mit dem HTML-Code fest zu verschweißen, anstatt diese sauber zu trennen. Gestalten Sie Ihre Website so, dass ohne JavaScript möglichst alle Inhalte zugänglich sind und alle Funktionen zur Verfügung stehen. Ihre JavaScripte schalten sich dann hinzu und modifizieren das Dokument entsprechend.

Anstatt also mittels noscript Inhalte einzufügen, die nur ohne JavaScript relevant ist, sollten Sie diese ganz normal ohne noscript im Dokument notieren. Falls der Inhalt bei aktiviertem JavaScript nicht benötigt wird, dann können Sie es mittels JavaScript verändern oder ganz ausblenden.

In den meisten Fällen werden Sie noscript also nicht brauchen. Es gibt einige Sonderfälle, in denen noscript angemessen ist: Etwa wenn es sich bei der Website um eine reine JavaScript-Webanwendung handelt, die (noch) keine Alternativversion anbietet. Dann können Sie mit noscript einen Hinweis darauf hinterlegen, dass die Website einen JavaScript-fähigen Browser zwingend voraussetzt.