Abschnitt 1 · Lektion 2
JavaScript einbinden
Ein Skript kommt auf zwei Wegen an eine Seite: als eigene Datei, auf die du verweist, oder als Block mitten im Dokument. Der erste Weg ist der Normalfall, der zweite die Ausnahme für ein paar Zeilen.
Beide brauchen dasselbe Element, script, und beide brauchen einen schließenden Tag. Auch der mit src, obwohl zwischen den beiden Tags nichts steht. Ein <script src="app.js" /> funktioniert im HTML nicht, das ist einer der Tippfehler, die einen Nachmittag kosten können.
Wann ein Skript läuft
Das ist die eigentliche Frage dieser Lektion, und sie ist wichtiger als die Schreibweise.
Der Browser liest ein Dokument von oben nach unten. Trifft er auf ein Skript, hält er an und führt es aus, bevor er weiterliest. Das heißt: was unter dem Skript steht, gibt es zu diesem Zeitpunkt noch nicht.
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>Zu früh</title>
<style>
body {
font-family: system-ui, sans-serif;
max-width: 40rem;
margin: 0;
padding: 1rem;
line-height: 1.6;
color: #1c1917;
}
h1 {
font-size: 1.5rem;
margin: 0 0 0.75rem;
}
</style>
<script>
// Dieser Block läuft sofort. Zu diesem Zeitpunkt gibt es die
// Überschrift unten noch nicht, der Browser ist noch nicht dort.
document.querySelector("h1").textContent = "Vom Skript geändert";
</script>
</head>
<body>
<h1>Die Überschrift</h1>
</body>
</html> Der Code sucht die Überschrift und will ihren Text ändern. Auf der Seite steht trotzdem der alte Text, und in der Console liegt ein Fehler: Cannot set properties of null (so schreibt es Chrome, in Firefox steht can't access property "textContent"). Der Selektor h1 ist völlig richtig, es gibt zu diesem Zeitpunkt nur noch keine Überschrift. document.querySelector hat nichts gefunden und null zurückgegeben, und null hat keinen Text, den man ändern könnte.
Das ist der häufigste Anfängerfehler mit JavaScript, und er sieht immer so aus, als wäre der Selektor schuld.
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>Spät genug</title>
<style>
body {
font-family: system-ui, sans-serif;
max-width: 40rem;
margin: 0;
padding: 1rem;
line-height: 1.6;
color: #1c1917;
}
h1 {
font-size: 1.5rem;
margin: 0 0 0.75rem;
}
</style>
</head>
<body>
<h1>Die Überschrift</h1>
<script>
// Derselbe Code, nur eine andere Stelle. Alles darüber steht schon.
document.querySelector("h1").textContent = "Vom Skript geändert";
</script>
</body>
</html> Derselbe Code, eine andere Stelle im Dokument, und plötzlich stimmt alles. Das Skript steht jetzt hinter der Überschrift, also existiert sie, wenn es läuft.
defer
Das Skript ans Ende des Body zu schieben, ist der alte Weg. Er funktioniert, und du wirst ihn in fremdem Code oft sehen. Heute gibt es dafür ein Attribut, und das ist die bessere Lösung.
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>Mit eigener Datei</title>
<style>
body {
font-family: system-ui, sans-serif;
max-width: 40rem;
margin: 0;
padding: 1rem;
line-height: 1.6;
color: #1c1917;
}
h1 {
font-size: 1.5rem;
margin: 0 0 0.75rem;
}
</style>
<script src="app.js" defer></script>
</head>
<body>
<h1>Die Überschrift</h1>
<script>
console.log("Dieser Block läuft.");
console.log("app.js gibt es hier nicht, die Zeile daraus fehlt.");
</script>
</body>
</html> defer sagt dem Browser: lad diese Datei ruhig schon herunter, aber führ sie erst aus, wenn das ganze Dokument steht. Damit bekommst du beides. Der Download beginnt früh, weil das Skript im Kopf steht und der Browser den Verweis sofort sieht. Die Ausführung passiert spät, wenn es alles findet, was es sucht.
In der Console siehst du dabei nur die beiden Zeilen aus dem Block, nicht die aus app.js. Diese Datei gibt es hier nicht, der Verweis geht ins Leere. Das ist Absicht: du sollst die Schreibweise sehen, ohne dass wir dafür eine zweite Datei erfinden.
Es gibt ein zweites Attribut, async, und die beiden werden gern verwechselt. async lädt ebenfalls parallel, führt das Skript aber aus, sobald es da ist, egal wie weit das Dokument gerade ist. Bei zwei Dateien läuft dann die schnellere zuerst, und das kann heute so und morgen anders sein. Für eigenen Code willst du das fast nie. async ist für Dinge gedacht, die mit dem Rest der Seite nichts zu tun haben, klassisch eine Statistik-Zählung.
Merksatz für den Anfang: defer ist der Normalfall, async die Ausnahme, und kein Attribut ist die schlechteste Wahl.
Ein Nachtrag, den du später brauchst: schreibst du <script type="module" src="app.js"></script>, verhält es sich automatisch wie defer. Module warten immer auf das fertige Dokument, ein zusätzliches defer ist dort überflüssig. Was ein Modul ist, kommt in Abschnitt 18.
Zwei Wege, ein Unterschied
Wann nimmt man welchen?
Eine eigene Datei nimmst du für alles, was mehr als drei Zeilen hat oder auf mehr als einer Seite gebraucht wird. Der Browser speichert sie zwischen, du siehst sie in deinem Editor mit Syntaxhervorhebung, und dein HTML bleibt lesbar.
Einen Block im Dokument nimmst du für wenige Zeilen, die genau zu dieser einen Seite gehören. Und für Beispiele wie die hier, wo eine zweite Datei nur im Weg wäre.
Beides gleichzeitig geht übrigens nicht in einem Tag: Ein script mit src ignoriert alles, was zwischen seinen Tags steht. Wer beides will, schreibt zwei Tags.
Wo du in diesem Kurs arbeitest
Ab der nächsten Lektion siehst du im Editor zwei Reiter: index.html mit dem Markup und app.js mit dem, woran du arbeitest. Ab Abschnitt 11 kommt manchmal ein styles.css dazu.
Das Skript ist dabei schon eingebunden, und zwar richtig. Du musst dich also nie um diesen Teil kümmern, außer in der Aufgabe gleich hier. In den nächsten Abschnitten wird index.html sogar ziemlich leer bleiben, denn bis Abschnitt 9 geht es um Werte, Rechnungen und Funktionen. Dein Ergebnis siehst du dort in der Console, und die ist das Thema der nächsten Lektion.
Die Reihenfolge der Reiter ist übrigens dieselbe wie im Dokument: erst das, was gegeben ist, zuletzt das, woran du arbeitest. Deshalb steht app.js vorn, wenn du eine Aufgabe öffnest.
Zum Mitnehmen
Ein Skript im Kopf ohne defer läuft, bevor es die Seite gibt. Es findet dann nichts, und der Fehler sieht aus, als wäre dein Selektor falsch.
Jetzt du
Basis Konto, kostenlosZu dieser Lektion gehört eine Aufgabe. Du schreibst den Code selbst, und nach jedem Lauf sagt dir eine Prüfliste, was schon stimmt.
Dafür brauchst du das Basis Konto. Es kostet nichts, und ein Passwort gibt es auch nicht.
Was in dieser Lektion steckt
-
Artikel mit 3 Beispielen zum Ausprobieren
Steht hier, ohne Konto lesbar.
-
Aufgabe im Editor, direkt im Browser geprüft
Öffnet sich mit dem Basis Konto.