JavaScript/Tutorials/DOM/Einbindung in HTML

Aus SELFHTML-Wiki
< JavaScript‎ | Tutorials‎ | DOM(Weitergeleitet von DOM-Scripting)
Wechseln zu: Navigation, Suche

Es gibt verschiedene Möglichkeiten, wie man ein JavaScript in ein Dokument einbindet. Ihnen allen liegt das script-Element zugrunde. Auf dieser Seite lernen Sie die Feinheiten kennen, die mit diesen verschiedenen Möglichkeiten verbunden sind, und warum es nicht die eine richtige Lösung gibt.

Einbindung

JavaScript-Quelltexte werden in HTML in einem script-Element notiert oder referenziert. Das script-Element darf dabei im body oder head des HTML-Dokuments notiert werden, auch innerhalb von flow content.

<script> in HTML5
<script>  </script>

JavaScript Code direkt in HTML notieren

Beispiel ansehen …
<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <title>JavaScript: Hallo Welt</title>
    <script>
      alert('Hallo Welt!');
    </script>
    <noscript>
      Sie haben JavaScript deaktiviert.
    </noscript>
  </head>
  <body>
    <p>Diese Seite tut nichts weiter, als eine Hinweisbox auszugeben.</p>
  </body>
</html>

Im obigen Beispiel wird mithilfe von JavaScript ein Meldungsfenster mit dem Text „Hallo Welt!“ am Bildschirm ausgegeben.

Empfehlung: Mit dem noscript-Element können Sie Bereiche definieren, die angezeigt werden, wenn JavaScript deaktiviert ist.

Früher war es üblich, Skripte in einen CDATA-Block einzuschließen. Die Notwendigkeit dafür bestand nur für XHTML-Dokumente, wurde aber von vielen als „das muss so sein“ aufgefasst und überall eingesetzt. Es hat auch nicht gestört. Für HTML 5 gibt es zwar auch eine XML Notation, aber solange Sie ein solches Dokument nicht mit einem XML Tool verarbeiten wollen, ist die CDATA-Notation nicht mehr notwendig.

Beachten Sie: Die Inhalte von script-Elementen (auch Inline-Scripts genannt) in einer HTML Datei werden vom HTML Parser normalerweise ignoriert. Sie müssen darin also nicht achtgeben, keine Zeichen wie & oder < zu verwenden, und ein HTML Kommentar innerhalb einer Zeichenkette wird ebenfalls nicht angetastet. Sie sollten es aber trotzdem vermeiden, innerhalb eines Inline-Scripts die Zeichenfolge --> zu verwenden. Der Versuch, einen solchen Script-Block mit einem HTML-Kommentar komplett auszublenden, führt dazu, dass der Kommentar dann bei dem --> im Script endet.

JavaScript in externer Datei auslagern

Das direkte Notieren von JavaScript in HTML ist im allgemeinen schlechte Praxis, weil es gegen das Prinzip der Trennung von Zuständigkeiten (englisch Separation of Concerns) verstößt. Den JavaScript-Code in einer externen Datei zu notieren und dann von dort aus einzubinden, bietet nicht nur den Vorteil, dass damit das HTML-Dokument selbst frei von JavaScript-Code ist, sondern auch, dass Sie damit den Code gleich auf mehreren Seiten verwenden können. Mit der Zeit werden Sie vielleicht eine ganze Sammlung an Skripten haben, die Sie für diverse Zwecke in ihren Dokumenten referenzieren.

externe JavaScript-Datei einbinden
<script src="pfad/zur/datei.js"></script>

Das src-Attribut (source = "Quelle") hat als Wert den URI zum Script. (Mehr Informationen über Referenzieren in HTML)

Die so referenzierte Datei ist eine simple Textdatei, die JavaScript-Code enthält und an der entsprechenden Stelle so ausgeführt wird, als ob der Code direkt notiert wurde. Die Datei-Endung ist im Grunde nicht auf *.js festgelegt, hat sich aber bewährt.

Wohin mit dem Script-Element?

Will man das Dokument so frei wie möglich von nicht-HTML-Dingen haben, dann bietet es sich an, im <head> des Dokuments einzubindende Script-Dateien zu notieren. Dieser Bereich des Dokuments beschäftigt sich mit ergänzenden Informationen über dieses Dokument, wie z.B. dem Titel des Dokuments, Angaben zu seiner Zeichenkodierung und eventueller anderer Meta-Angaben. Auch referenzierte Layoutdateien stehen hier verlinkt. So gesehen haben Script-Referenzen genau hier ihren Platz.

Problem mit unterschiedlichen Zeitlichkeiten

Wenn jetzt in einer solchen Script-Datei erwartet wird, dass sich ganz bestimmte Dinge im Dokument finden werden, mit denen das Script dann etwas tun soll, gibt es ein Problem mit dem Ausführungszeitpunkt. Vereinfachen wir das Problem, indem wir den Code direkt im Dokument notieren:

Zeitlichkeiten bei eingebundenen Scriptdateien
<!DOCTYPE html>
<html lang="de">
<head>
  <meta charset="utf-8">
  <title>Zeitlichkeiten</title>
  <script>alert(document.getElementById("los"));</script>
</head>
<body>
  <h1>Zeitlichkeiten</h1>
  <button id="los">Los!</button>
  <script>alert(document.getElementById("los"));</script>
</body>
</html>
In beiden script-Elementen passiert das Gleiche: Es wird ein HTML-Element gesucht, das über seine ID gefunden werden soll. Der Fund wird über die Funktion alert ausgegeben.

Das erste script-Element wird ausgeführt, bevor der Browser den Dokumenteninhalt fertig gestellt hat. Alles, was im <body>-Element notiert ist, ist zu diesem Zeitpunkt noch nicht verfügbar. Daher wird im ersten alert-Aufruf ein undefined ausgegeben werden.

Das zweite script-Element steht am Ende von <body>, weshalb bei seiner Ausführung ein passendes HTML-Element gefunden werden kann.

Das Ereignis DOMContentLoaded

Auch wenn es gerade so aussieht, als müsse aus Gründen der verschiedenen Zeitlichkeiten das script-Element ans Ende des body-Elements, ist das überhaupt kein Muss. Es gibt nämlich die Möglichkeit, die Ausführung von einem Browser-Ereignis abhängig zu machen. Wenn der Browser das DOM aufgebaut hat, löst er ein ganz bestimmtes Ereignis aus: DOMContentLoaded. So können Sie erreichen, dass Ihr JavaScript erst beim Eintreten von diesem Ereignis ausgeführt wird:

Ausführung bei DOMContentLoaded
<!DOCTYPE html>
<html lang="de">
<head>
  <meta charset="utf-8">
  <title>Zeitlichkeiten</title>
  <script>
    document.addEventListener("DOMContentLoaded", function () {
      // jetzt mein Code
      alert(document.getElementById("los"));
    });
  </script>
</head>
<body>
  <h1>Zeitlichkeiten</h1>
  <button id="los">Los!</button>
</body>
</html>
Im script-Element wird eine anonyme Funktion als zweites Argument an den Aufruf addEventListener() übergeben. Das erste Argument ist eine Zeichenkette, die den Namen des Ereignisses enthält, bei dessen Eintreten die anonyme Funktion ausgeführt werden soll.

Verpackt man seinen Code auf diese Weise, kann man erreichen, dass vom Browser das DOM bereits vollständig aufgebaut worden ist, ehe man mit seinem JavaScript auf Elemente darin zugreift.

Unterschiede in der Performance je nach Position im Dokument

Eine immer wieder gestellte Frage ist, welche der beiden Positionen besser ist.

Wenn ein Browser eine Webseite lädt, folgt er einer festen Routine:

  1. Der Browser holt sich die HTML-Datei
  2. Das HTML-Markup wird geparst
  3. Der Parser trifft auf ein script-Element, das ein externes JavaScript referenziert
  4. Der Browser sendet einen Request, dass das Skript geladen werden soll. In der Zwischenzeit wird das Parsen des restlichen HTML-Markups eingestellt.
  5. Nach einiger Zeit ist das Skript heruntergeladen und wird ausgeführt.
  6. Der Browser setzt mit dem Parsen des restlichen HTML fort.

Warum kommt es bei Schritt 4 zu einer Unterbrechung? Skripte können mit so veralteten Ungetümen wie document.write() oder auch den moderneren DOM-Methoden Manipulationen am HTML vornehmen oder das bisherige DOM verändern. Deshalb wartet der Parser, bis das Skript heruntergeladen und ausgeführt ist, bevor er den Rest des Dokuments parst.

Aus diesem Grund empfahlen früher viele, JavaScript am Schluss einzubinden, indem das script-Element vor das schließende body-Tag gesetzt wurde. Dieser Ansatz birgt aber das Problem, dass dadurch der Browser die Skripte erst downloaden kann, wenn das ganze HTML-Dokument geladen und geparst ist. Gerade bei größeren Seiten mit vielen Stylesheets, Skripten und eingebundenen Bibliotheken kann dies zu einer schlechteren Konversionsrate führen, wenn Nutzer nach erfolglosem Warten auf die Webseite entnervt aufgeben.

Also sollten Skripte so schnell wie möglich geladen werden.

Attribute im script-Element

Durch die Attribute async und defer können Sie dem Browser mitteilen, dass er mit dem Parsen fortfahren kann, während er Skripte herunterlädt.

Eine weitere Alternative sind ECMASCript 2015-Module. Sie werden durch ein <script>-Element mit type="module" geladen und verhalten sich bezüglich ihres Timings genau wie defer-Scripte. Sie setzen allerdings einen ECMAScript 2015-fähigen Browser voraus und sind nicht geeignet, wenn Altbrowser unterstützt werden sollen.

Beispiel
<script src="script1.js" async></script>
<script src="script2.js" async></script>
Beachten Sie: Sie müssen beim Gebrauch von async und defer die Verarbeitungsreihenfolge des Browsers im Auge behalten.

Die wenigen alten Browser, die diese Attribute noch nicht kennen, ignorieren sie. Statt dessen wird Ihr Script synchron geladen und sofort ausgeführt. Wollen Sie solche Browser unterstützen, müssen Sie Ihr Script so schreiben, dass es zu jedem Ausführungszeitpunkt funktioniert.

Hinweis:
Bei den Beispielen im Wiki sollten Sie JavaScript immer im head einbinden, da im body vorhandene Skripte im Frickl-Editor zwar angezeigt, beim Ausführen durch den im HTML-Markup vorhandenen OriginalCode aber wieder überschrieben werden. --MScharwies (Diskussion) 11:55, 11. Jan. 2017 (CET)
Beachten Sie:
  • In HTML5 können Sie die Angabe des MIME-Type type="text/javascript" weglassen - in älteren HTML-Varianten war die Angabe notwendig.
  • Das language-Attribut wurde als deprecated eingestuft und muss weggelassen werden.

Timing von defer-Scripten

Man kann keine Aussage darüber treffen, wann ein mit defer oder async markiertes Script zur Verfügung steht. Bei defer-Scripten wissen Sie bei einem nicht zu alten Browser aber immerhin genau, wann es gestartet wird: Nach dem Parsen des DOM, und vor dem Auslösen des DOMContentLoaded-Events. Ebenfalls ist festgelegt, dass defer-Scripte in der Reihenfolge ausgeführt werden, wie sie im HTML angetroffen werden.

Wenn Ihr defer-Script mit der Ausführung an der Reihe ist, aber noch nicht fertig geladen wurde, wartet der Browser.

Für eine Altbrowser-Unterstützung sollten Sie

  • den Initialisungscode Ihres Scripts, der vom DOM abhängt, in eine Funktion verpacken
  • zu Beginn einen Test einbauen, ob ein HTML Element, das im Quellcode hinter Ihrem Script steht, im DOM gefunden werden kann.

Wird das Element gefunden, hat defer funktioniert und Sie können die Initialisierungsfunktion sofort aufrufen.
Wenn nicht, müssen Sie die Ihre Initialisierungsfunktion als Eventhandler für DOMContentLoaded registrieren.

Timing von async-Scripten

Ein async-Script wird vom Browser ebenfalls im Hintergrund geladen. Sobald es bereit steht (und gerade kein anderes Script läuft), wird es gestartet. Das kann noch während des Parsens der Seite geschehen, zwischen zwei defer-Scripten oder auch erst, nachdem die Seite schon längst angezeigt wird.

Sie können sich daher weder darauf verlassen, welcher Anteil des DOM bereits verfügbar ist, noch darauf, dass ein von Ihnen registrierter DOMContentLoaded Handler noch an die Reihe kommt. Eine Möglichkeit, sich über den Zustand der Seite zu orientieren, ist die Eigenschaft readystate des document-Objekts. Finden Sie dort den Wert "loading" vor, dann wurde DOMContentLoaded noch nicht ausgeführt und Sie sollten Ihre Initialisierung in einem Handler für dieses Event ausführen. Bei anderen Werten von readyState sollte das DOM bereitstehen.

Für eine Altbrowser-Unterstützung ist das allerdings nicht hinreichend, weil bis zum Internet Explorer 10 auf readyState kein Verlass ist. Hier ist es sicherer, im DOM ein möglichst weit am Ende des HTML Quelltextes befindliches Element zu suchen. Wenn es fehlt, registrieren Sie einen DOMContentLoaded Handler für die Initialisierung (ab Internet Explorer 9).

Timing bei gleichzeitiger Angabe von defer und async

Grundsätzlich hat in diesem Fall das async-Attribut Vorrang. Es gibt jedoch alte Browser, die async nicht kennen, defer aber schon. Um in solchen Browsern asynchron geladene Scripte anbieten zu können, kann man beide Attribute angeben. Ein besonderer Programmieraufwand im Script entsteht deswegen nicht. Wenn Ihr Script mit async zurecht kommt, dann funktioniert es auch mit defer.

Interaktion von async-Scripten

Wenn Sie mehrere async-Scripte laden und in einem Script darauf angewiesen sind, dass ein anderes async-Script bereits gelaufen ist, dann sollten Sie Ihr Design überarbeiten. Es gibt keine Garantie für die Reihenfolge, in der async-Scripte gestartet werden. Über ein im DOM verstecktes Element oder Attribut könnten Sie zwar feststellen, ob das vorausgesetzte Script bereits gelaufen ist, aber wenn nicht, gibt es keinen fertigen Mechanismus, um auf das fehlende Script zu warten. Natürlich kann man einen solchen Mechanismus bauen, aber Sie sollten das Rad hier nicht neu erfinden. Versuchen Sie, Ihr Problem mit defer-Scripten oder ECMAScript 2015-Modulen zu lösen, oder verwenden Sie eine JavaScript-Bibliothek wie require.js zur Koordination der Modulabhängigkeiten.

Steuerung der Script-Ausführung in einem async-Script
Nehmen wir an, Ihr async-Script möchte einen click-Handler für den Button mit der ID "speichernButton" registrieren. Die eigentliche Arbeit des Scripts ist in der Funktion registriereClickHandler gebündelt. Der Vorspann des Scripts prüft zunächst, ob der Button bereits zu finden ist. Wenn ja, ruft er die Registrierfunktion direkt auf. Andernfalls wird sie als EventHandler für DOMContentLoaded registriert.
if (document.getElementById("speichernButton"))
   registriereClickHandler()
else
   document.addEventListener("DOMContentLoaded", registriereClickHandler);

function registriereClickHandler() {
   document.getElementById("speichernButton")
           .addEventListener("click", function(event) {
              // Code zum Speichern hier
           });
}


Weblinks