Abschnitt 10 · Lektion 4
Wenn nichts gefunden wird
„Cannot read properties of null” (Firefox: „can’t access property …, … is null”) ist die mit Abstand häufigste Fehlermeldung beim Einstieg ins DOM. Ihre Ursache ist immer dieselbe, ihre Behandlung auch, und deshalb bekommt sie eine eigene Lektion statt einer Fußnote.
Woher der Fehler kommt
<h1 id="titel">Meine Seite</h1> 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;
} // Ein Treffer: alles normal.
console.log("gefunden:", document.querySelector("#titel").textContent);
// Kein Treffer: null. Kein Fehler, noch nicht.
console.log("nicht gefunden:", document.querySelector("#gibtesnicht"));
// Der Fehler kommt erst beim nächsten Schritt. Eine
// Eigenschaft von null zu lesen geht nicht.
// Diese Zeile steht deshalb ganz unten.
console.log(document.querySelector("#gibtesnicht").textContent);
console.log("Diese Zeile wird nie erreicht"); querySelector liefert null, wenn nichts passt. Das allein ist kein Fehler, und genau das macht es unangenehm: die Zeile mit dem querySelector läuft durch.
Der Fehler kommt eine Zeile später, sobald jemand eine Eigenschaft von null liest. Das ist derselbe TypeError wie in Lektion 7.4, nur dass er hier nicht aus fehlenden Daten kommt, sondern aus einem Element, das es nicht gibt.
Und wie jeder Fehler beendet er das Skript an dieser Stelle. Es geht also nicht nur eine Zeile verloren, sondern alles danach. Wenn deine Seite plötzlich gar nichts mehr tut, steht in der Console meistens genau diese Meldung, und ein Klick auf die Fundstelle darin bringt dich an die Zeile im Editor.
Die Ursachen
<h1 class="titel">Meine Seite</h1> 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;
} // Ursache 1: Tippfehler oder falscher Selektor. Hier steht
// eine Klasse im Markup, gesucht wird eine id.
console.log("mit Raute:", document.querySelector("#titel"));
console.log("mit Punkt:", document.querySelector(".titel"));
// Ursache 2: Groß- und Kleinschreibung im Selektor.
console.log("groß:", document.querySelector(".Titel"));
// Ursache 3: das Element gibt es erst später. Hier noch nicht.
console.log("noch nicht da:", document.querySelector("#spaeter"));
const neu = document.createElement("div");
neu.id = "spaeter";
document.body.append(neu);
console.log("jetzt da:", document.querySelector("#spaeter").id);
// Die vierte Ursache kannst du hier nicht sehen: ein Skript,
// das vor dem Markup läuft, findet gar nichts. Deshalb defer,
// siehe Lektion 1.2. Es sind immer dieselben, und man kommt schneller ans Ziel, wenn man sie der Reihe nach durchgeht.
Erstens der Selektor. Eine Klasse im Markup, eine id im Code. Punkt statt Raute. Ein Bindestrich zu viel. Das ist der häufigste Fall, und er sieht beim Lesen richtig aus.
Zweitens die Schreibweise. Selektoren für Klassen und id unterscheiden Groß- und Kleinschreibung, .Titel findet .titel nicht.
Drittens der Zeitpunkt. Das Element gibt es noch nicht, weil es erst durch eine Aktion entsteht. Das ist in Abschnitt 11 der Normalfall und dort kein Problem, solange man in der richtigen Reihenfolge arbeitet.
Und viertens, was du im Beispiel nicht sehen kannst: ein Skript, das vor dem Markup läuft, findet gar nichts. Genau deshalb steht defer in Lektion 1.2 so weit vorn. Wenn querySelector jedes Element auf der Seite nicht findet, ist das fast immer die Ursache.
Zwei Behandlungen
<h1 id="titel">Meine Seite</h1> 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;
} // Behandlung 1: früh aussteigen. Passt, wenn ohne das
// Element nichts Sinnvolles mehr zu tun ist.
function fuellen(selektor) {
const el = document.querySelector(selektor);
if (!el) return;
console.log("gefüllt:", el.textContent);
}
fuellen("#titel");
fuellen("#gibtesnicht");
console.log("läuft weiter");
// Behandlung 2: optional zugreifen. Passt, wenn du einen
// Ersatzwert hast und weitermachen willst.
const text = document.querySelector("#gibtesnicht")?.textContent ?? "nicht gefunden";
console.log("mit ?.:", text);
// Und wo das Fehlen ein echter Fehler ist, lässt man es
// krachen. Eine stille Seite ohne Wirkung ist schlimmer als
// eine laute Meldung in der Console. Früh aussteigen passt, wenn ohne das Element nichts Sinnvolles mehr zu tun ist: if (!el) return; als zweite Zeile der Funktion, und der Rest darf sich darauf verlassen, dass es das Element gibt. Das ist dieselbe Grenze wie in Lektion 8.5.
Optional zugreifen passt, wenn du einen Ersatzwert hast und weitermachen willst: ?. und ?? aus Lektion 7.4, hier mit einem Element statt mit Daten.
Und manchmal ist die richtige Behandlung: gar keine. Wenn ein Element fehlt, das da sein müsste, ist das ein Fehler in der Seite und keine Situation, die man abfangen sollte. Eine laute Meldung in der Console ist dann besser als eine Seite, die stillschweigend nichts tut. Wer jeden querySelector in ein ?. packt, verliert genau diese Auskunft.
Und der andere Fall
<h1>Meine Seite</h1> 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;
} // querySelectorAll liefert nie null, sondern eine leere Liste.
const nichts = document.querySelectorAll(".gibtesnicht");
console.log("Typ:", typeof nichts, "| Länge:", nichts.length);
// Deshalb tut eine Schleife darüber einfach nichts.
nichts.forEach((el) => console.log("das kommt nie"));
console.log("und läuft weiter");
// Auch Array.from und map bleiben leer statt zu werfen.
console.log("map:", Array.from(nichts).map((el) => el.textContent));
// Das ist bequem und hat eine Kehrseite: ein Tippfehler im
// Selektor fällt hier nicht auf. Es passiert einfach nichts,
// und niemand sagt dir, warum.
const karten = document.querySelectorAll(".kartee");
console.log("Anzahl:", karten.length); querySelectorAll verhält sich völlig anders: es liefert nie null, sondern eine leere Liste.
Deshalb kracht dort nichts. Eine Schleife über eine leere Liste läuft null Mal, Array.from liefert ein leeres Array, map ebenfalls. Alles bleibt friedlich.
Die Kehrseite ist, dass ein Tippfehler im Selektor nicht auffällt. Es passiert einfach nichts, und niemand sagt dir, warum. Bei querySelector bekommst du wenigstens eine Fehlermeldung.
Der praktische Rat daraus: wenn eine Schleife über Elemente scheinbar nichts tut, gib als Erstes die length aus. Steht dort 0, ist der Selektor schuld und nicht die Schleife.
Zum Mitnehmen
querySelector liefert null, querySelectorAll eine leere Liste. Der eine Fall kracht beim nächsten Zugriff, der andere tut einfach nichts, und dieser Unterschied erklärt die halbe Fehlersuche in diesem Abschnitt.
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 4 Beispielen zum Ausprobieren
Steht hier, ohne Konto lesbar.
-
Aufgabe im Editor, direkt im Browser geprüft
Öffnet sich mit dem Basis Konto.