Abschnitt 11 · Lektion 6
Elemente entfernen und ersetzen
Was dazukommen kann, muss auch wieder wegkönnen. Das Entfernen selbst ist eine Zeile, die Falle darin ist die Schleife.
remove und replaceWith
<ul id="liste">
<li id="weg">Kommt weg</li>
<li id="tausch">Wird getauscht</li>
<li>Bleibt stehen</li>
</ul> body {
font-family: system-ui, sans-serif;
max-width: 40rem;
margin: 0;
padding: 1rem;
line-height: 1.6;
color: #1c1917;
} const liste = document.getElementById("liste");
// remove nimmt das Element aus dem Baum. Kein Elternteil nötig.
const weg = document.getElementById("weg");
weg.remove();
console.log("Elternteil danach:", weg.parentElement);
// Das Objekt gibt es weiterhin. Man kann es wieder einhängen.
liste.append(weg);
console.log("wieder drin:", liste.children.length);
weg.remove();
// replaceWith tauscht ein Element gegen ein anderes.
const ersatz = document.createElement("li");
const stark = document.createElement("strong");
stark.textContent = "Neu an dieser Stelle";
ersatz.append(stark);
document.getElementById("tausch").replaceWith(ersatz);
for (const el of liste.children) console.log(el.outerHTML); el.remove() nimmt das Element aus dem Baum. Du brauchst dafür weder das Elternteil noch sonst etwas, das Element kennt seinen Platz selbst.
Weg ist es damit nur von der Seite. Das Objekt existiert weiter, solange dein Code eine Variable darauf hält, und du kannst es an anderer Stelle wieder einhängen. Genau das nutzt man beim Umsortieren.
el.replaceWith(anderes) tauscht ein Element gegen ein anderes, an derselben Stelle. Das ist kürzer als „das alte entfernen und das neue an dieselbe Position einhängen”, und man muss sich die Position nicht merken.
Die Falle: children lebt
<ul id="lebend">
<li>A</li><li>B</li><li>C</li><li>D</li>
</ul>
<ul id="fest">
<li>A</li><li>B</li><li>C</li><li>D</li>
</ul> body {
font-family: system-ui, sans-serif;
max-width: 40rem;
margin: 0;
padding: 1rem;
line-height: 1.6;
color: #1c1917;
} const lebend = document.getElementById("lebend");
// children ist eine LEBENDE Liste: sie schrumpft mit.
// Nach dem ersten remove rutscht B auf Position 0, und
// die Schleife schaut als Nächstes auf Position 1.
for (let i = 0; i < lebend.children.length; i++) {
lebend.children[i].remove();
}
console.log("lebend, übrig:", lebend.children.length);
for (const el of lebend.children) console.log("übrig:", el.textContent);
// querySelectorAll liefert eine Momentaufnahme. Sie ändert
// sich nicht mehr, egal was danach mit dem Baum passiert.
const fest = document.getElementById("fest");
for (const el of fest.querySelectorAll("li")) {
el.remove();
}
console.log("fest, übrig:", fest.children.length); liste.children ist eine lebende Sammlung. Sie ist kein Abbild vom Moment des Zugriffs, sondern ein Fenster auf den aktuellen Stand: entfernst du ein Kind, wird sie sofort kürzer.
Damit geht die naheliegende Schleife schief. Bei vier Einträgen entfernt sie A, worauf B auf Position 0 rutscht. Die Schleife zählt aber weiter auf 1, sieht dort C und entfernt es. Übrig bleiben B und D, also jedes zweite Element.
Das Tückische daran: es sieht nach einem Zufall aus. Bei zwei Einträgen bleibt einer stehen, bei fünf bleiben zwei, und wer es mit einem einzigen Eintrag ausprobiert, findet gar keinen Fehler.
Dass children wirklich lebt, kannst du in der Eingabezeile der Console Schritt für Schritt nachstellen, denn eine Bindung dort überlebt die nächste Eingabe. Tipp const kinder = lebend.children, dann kinder.length: 2, die beiden übrig gebliebenen. Dann lebend.firstElementChild.remove() und wieder kinder.length: 1. Niemand hat kinder angefasst, und die Liste ist trotzdem kürzer. Jetzt die Gegenprobe: const momentaufnahme = lebend.querySelectorAll("li"), momentaufnahme.length antwortet 1. Noch ein lebend.firstElementChild.remove(), und momentaufnahme.length antwortet weiter 1, während kinder.length jetzt 0 sagt.
querySelectorAll ist davon nicht betroffen. Es liefert eine Momentaufnahme, die sich nicht mehr ändert. Über sie kannst du laufen und dabei nach Belieben am Baum arbeiten. Deshalb steht in diesem Kurs beim Entfernen immer querySelectorAll.
Dieselbe Unterscheidung gilt für children gegen querySelectorAll, für parentElement.children und für alle Sammlungen, die der Browser von sich aus führt. Der Merksatz: was querySelectorAll liefert, ist eine Momentaufnahme, was der Baum selbst führt, lebt. Die Elemente darin sind dieselben, nur die Liste wächst und schrumpft nicht mehr mit.
Leeren
<ul id="eins"><li>A</li><li>B</li></ul>
<ul id="zwei"><li>A</li><li>B</li></ul>
<ul id="drei"><li>A</li><li>B</li></ul> body {
font-family: system-ui, sans-serif;
max-width: 40rem;
margin: 0;
padding: 1rem;
line-height: 1.6;
color: #1c1917;
} // Der kürzeste Weg, und der klarste.
document.getElementById("eins").replaceChildren();
// Der Weg, der überall steht. Für selbst gebauten Inhalt
// in Ordnung, und er kostet den Browser einen Parser-Lauf
// über eine leere Zeichenfolge.
document.getElementById("zwei").innerHTML = "";
// Der ausführliche Weg. Er läuft von vorn, solange noch
// ein Kind da ist, und kann deshalb nichts überspringen.
const drei = document.getElementById("drei");
while (drei.firstElementChild) {
drei.firstElementChild.remove();
}
console.log(
document.getElementById("eins").children.length,
document.getElementById("zwei").children.length,
drei.children.length
); Drei Wege, dasselbe Ergebnis.
replaceChildren() ohne Argument ist der klarste: Der Name sagt, was passiert, und mit Argumenten ersetzt dieselbe Methode den Inhalt in einem Rutsch. In diesem Kurs ist das der Standard.
innerHTML = "" steht in der Praxis überall, und für selbst gebauten Inhalt ist es in Ordnung. Es ist der einzige Fall, in dem innerHTML nichts anrichten kann: an einer leeren Zeichenfolge gibt es nichts zu schmuggeln.
Die while-Schleife über firstElementChild ist der ausführliche Weg. Sie greift immer das erste Kind und kann deshalb nichts überspringen, ganz gleich wie lebendig die Liste ist.
Leeren und neu bauen, oder gezielt ändern
Zwei Vorgehensweisen, und beide sind richtig.
Leeren und neu aufbauen ist einfach zu schreiben und einfach zu verstehen: Es gibt einen Zustand, und aus ihm entsteht die ganze Ansicht. Kein Vorher-Nachher, kein Vergessen. Das ist das Muster von Lektion 11.7, und im Kern ist es das, was Frameworks später automatisieren.
Gezielt ändern ist sparsamer und in zwei Fällen nötig: wenn die Liste sehr groß ist, oder wenn im Bereich etwas steckt, das ein Neuaufbau zerstören würde, etwa der Text in einem Eingabefeld oder die Position im Video.
Für alles in diesem Kurs reicht das Neuaufbauen. Fang damit an und wechsle erst, wenn du einen Grund messen kannst.
Zum Mitnehmen
children ist eine lebende Liste. Wer beim Entfernen darüberläuft, überspringt jedes zweite Element. querySelectorAll ist davon nicht betroffen.
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.