Zum Inhalt springen
aviral gupta

// B6.2 · ca. 30 Min. · Einstieg

Elemente erzeugen und entfernen

Nach dieser Lektion bauen Sie neue Elemente, setzen sie an die richtige Stelle, entfernen sie wieder und rendern eine ganze Liste aus einem Array von Daten.

Lektion 2 von 5 in B6 Das DOM und Ereignisse

Danach können Sie

  • Elemente mit createElement erzeugen, mit textContent füllen und mit append und prepend einfügen
  • Elemente mit remove() entfernen und eine Liste mit replaceChildren() leeren oder ersetzen
  • Eine Liste aus einem Array bauen, mit einem <template> oder einem DocumentFragment, und sie aus den Daten neu rendern, statt Knoten einzeln zu bearbeiten
  1. Aufwärmen · Aufgabe 1 von 7

    Zum Aufwärmen aus Lektion B6.1. Die Liste ist <ul id="list"><li>html</li><li>css</li></ul>. Was gibt dieser Code aus?

    const names = [...document.querySelectorAll('#list li')].map((li) => li.textContent);
    console.log(names.length, names[1]);
  2. Vorhersagen · Aufgabe 2 von 7

    Sagen Sie voraus, bevor Sie weiterlesen. Die Seite hat ein leeres <ul id="list">. Was gibt dieser Code aus?

    const li = document.createElement('li');
    li.textContent = 'tea';
    console.log(document.querySelectorAll('li').length, li.textContent);
  3. Üben · Aufgabe 3 von 7

    Ergänzen Sie die Methode, die li hinter den letzten Eintrag der Liste setzt.

    list.____(li);
    list.(li);
  4. Üben · Aufgabe 4 von 7

    Ordnen Sie jedem Aufruf zu, was er mit der Seite macht.

  5. Üben · Aufgabe 5 von 7

    Die Liste enthält html und css. Was gibt dieser Code aus?

    const list = document.querySelector('#list');
    const li = document.createElement('li');
    li.textContent = 'new';
    list.prepend(li);
    console.log(list.firstElementChild.textContent, list.children.length);
  6. Denksport · Aufgabe 6 von 7

    Knobelaufgabe. Die Liste ist zu Beginn leer. Was gibt die letzte Zeile aus?

    const list = document.querySelector('#list');
    const li = document.createElement('li');
    for (const name of ['a', 'b', 'c']) {
      li.textContent = name;
      list.append(li);
    }
    console.log(list.children.length, list.textContent);
  7. Anwenden · Aufgabe 7 von 7

    Mini-Aufgabe: eine Filmliste, aus Daten gerendert. Schreiben Sie eine index.html mit einem leeren <ul id="films">. Halten Sie in main.js ein Array mit drei Filmen und schreiben Sie render(), das pro Film ein li mit createElement und textContent baut und alle mit replaceChildren einsetzt. Rufen Sie render() auf, fügen Sie mit push einen vierten Film hinzu und rufen Sie render() erneut auf: Die Liste muss vier Filme zeigen, nicht sieben. Entfernen Sie dann den ersten Eintrag mit remove() und geben Sie die übrigen Texte aus.

    Prüfen Sie Ihr Ergebnis anhand dieser Liste

Selbst programmieren

Lesen Sie das ausgearbeitete Beispiel und lösen Sie dann die Übungen. Ihr Code läuft in Ihrem Browser oder auf Ihrem Computer und wird nie hochgeladen.

Ausgearbeitetes Beispiel

Eine Playlist, aus Daten gerendert

Die Seite beginnt mit einem Platzhalter-Eintrag und einem <template> für eine Song-Zeile. main.js entfernt den Platzhalter, dann baut render() für jeden Song eine Zeile aus dem Template und setzt alle Zeilen mit replaceChildren in die Liste. Nachdem ein Song zum Array hinzugekommen ist, läuft render() erneut, und die Liste hat drei Zeilen, nicht fünf. Zuletzt wird ein mit createElement erzeugtes Element mit prepend an den Anfang gesetzt.

main.js

const songs = [
  { name: 'Blue', seconds: 245 },
  { name: 'Halo', seconds: 198 }
];
const list = document.querySelector('#songs');
const template = document.querySelector('#song-row');

// The placeholder is not needed once the script runs.
document.querySelector('#loading').remove();
console.log('after remove:', list.children.length);

// One row from the template: copy it, then fill the copy.
function row(song) {
  const copy = document.importNode(template.content, true);
  copy.querySelector('.name').textContent = song.name;
  copy.querySelector('.length').textContent = song.seconds + ' s';
  return copy;
}

// Builds the whole list from the data, replacing what was there.
function render() {
  list.replaceChildren(...songs.map(row));
}

render();
console.log('first render:', list.children.length);
songs.push({ name: 'Home', seconds: 130 });
render();
console.log('second render:', list.children.length);

// A single element made by hand, put at the start.
const now = document.createElement('li');
now.textContent = 'Now playing: ' + songs[0].name;
now.classList.add('now');
list.prepend(now);
console.log(list.firstElementChild.textContent, list.children.length);

index.html

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Playlist</title>
</head>
<body>
  <h1>Playlist</h1>
  <ul id="songs"><li id="loading">Loading...</li></ul>
  <template id="song-row"><li class="song"><span class="name"></span> <span class="length"></span></li></template>
  <script type="module" src="main.js"></script>
</body>
</html>

Um die Seite auf Ihrem Computer auszuführen, öffnen Sie index.html über einen lokalen Webserver, nicht als Datei: Browser führen Modul-Skripte von file://-Adressen nicht aus. Für die Prüfungen installieren Sie zuerst jsdom (die Befehle unten).

Ausgabe

after remove: 0
first render: 2
second render: 3
Now playing: Blue 4
<!-- #songs -->
<ul id="songs"><li class="now">Now playing: Blue</li><li class="song"><span class="name">Blue</span> <span class="length">245 s</span></li><li class="song"><span class="name">Halo</span> <span class="length">198 s</span></li><li class="song"><span class="name">Home</span> <span class="length">130 s</span></li></ul>
  • remove() hat den Platzhalter herausgenommen, die Liste war vor dem ersten render also leer.
  • Das zweite render hat die zwei Zeilen durch drei ersetzt: replaceChildren fügt nie zum Vorhandenen hinzu.
  • Jede Zeile ist eine gefüllte Kopie des Templates; das Template selbst bleibt leer und unsichtbar.
  • prepend hat das von Hand gebaute li vor die erste Zeile gesetzt.
Ändern und ausführen

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser den JavaScript-Runner herunter (bis zu 0.1 MB) und speichert ihn im Cache. Ihr Code läuft in der eigenen Engine Ihres Browsers und bleibt auf Ihrem Gerät.

Ihre Seite

Ihre Seite läuft in einem abgeschotteten Rahmen. Sie hat keinen Zugriff auf diese Website, und ihr localStorage liegt nur für diesen Lauf im Speicher: Der nächste Lauf beginnt leer.

Führen Sie das Programm aus, um seine Seite hier zu sehen.

Übungen

Übung 1 von 2

Eine Einkaufsliste aus Daten rendern

render() soll die Liste aus dem Array items bauen: ein li pro Eintrag, mit dem Eintrag als Text, und den Absatz #count auf "4 items" setzen, nachdem der vierte hinzugekommen ist. Das Programm ruft render() auf, fügt tea hinzu und ruft render() erneut auf. Der Starter hängt bei jedem Aufruf an, der zweite Aufruf verdoppelt also die Liste, und er setzt die Anzahl nie. Lassen Sie render() stattdessen die Kinder der Liste ersetzen.

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser den JavaScript-Runner herunter (bis zu 0.1 MB) und speichert ihn im Cache. Ihr Code läuft in der eigenen Engine Ihres Browsers und bleibt auf Ihrem Gerät.

Ihre Seite

Ihre Seite läuft in einem abgeschotteten Rahmen. Sie hat keinen Zugriff auf diese Website, und ihr localStorage liegt nur für diesen Lauf im Speicher: Der nächste Lauf beginnt leer.

Führen Sie das Programm aus, um seine Seite hier zu sehen.

Hinweise
  1. Hinweis 1

    Führen Sie die Tests aus: Die Liste zeigt bread, milk und eggs doppelt, weil das zweite render() an das erste anhängt.

  2. Hinweis 2

    Bauen Sie zuerst alle li-Elemente, zum Beispiel mit items.map, und geben Sie jedes zurück.

  3. Hinweis 3

    Setzen Sie sie mit list.replaceChildren(...lis) ein und dann document.querySelector('#count').textContent.

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

const items = ['bread', 'milk', 'eggs'];
const list = document.querySelector('#list');

// Builds the list from items. It runs again whenever items change.
function render() {
  const lis = items.map((item) => {
    const li = document.createElement('li');
    li.textContent = item;
    return li;
  });
  list.replaceChildren(...lis);
  document.querySelector('#count').textContent = items.length + ' items';
}

render();
items.push('tea');
render();
Auf dem eigenen Computer ausführen

Installieren Sie ECMAScript 2026 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.js

const items = ['bread', 'milk', 'eggs'];
const list = document.querySelector('#list');

// Builds the list from items. It runs again whenever items change.
function render() {
  for (const item of items) {
    const li = document.createElement('li');
    li.textContent = item;
    list.append(li);
  }
  // TODO: the count, such as "4 items".
}

render();
items.push('tea');
render();

main.test.js

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {loadPage} from './learnrun.js';

const shown = (page) => [...page.document.querySelectorAll('#list li')].map((li) => li.textContent);

test('Die Liste zeigt jeden Eintrag einmal, in der richtigen Reihenfolge', async () => {
  const page = await loadPage();
  assert.deepEqual(shown(page), ['bread', 'milk', 'eggs', 'tea'], 'die Liste zeigt ' + JSON.stringify(shown(page)));
});

test('Die Liste hat genau vier Einträge', async () => {
  const page = await loadPage();
  const n = page.document.querySelector('#list').children.length;
  assert.equal(n, 4, 'die Liste hat ' + n + ' Kinder');
});

test('Die Anzahl lautet 4 items', async () => {
  const page = await loadPage();
  const text = page.document.querySelector('#count').textContent;
  assert.equal(text, '4 items', 'die Anzahl lautet ' + JSON.stringify(text));
});

index.html

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Shopping</title>
</head>
<body>
  <h1>Shopping</h1>
  <ul id="list"></ul>
  <p id="count"></p>
  <script type="module" src="main.js"></script>
</body>
</html>

package.json

{
  "type": "module"
}

package.json sagt Node.js, dass die .js-Dateien Module sind; lassen Sie die Datei im Ordner.

Programm ausführen:

Um die Seite auf Ihrem Computer auszuführen, öffnen Sie index.html über einen lokalen Webserver, nicht als Datei: Browser führen Modul-Skripte von file://-Adressen nicht aus. Für die Prüfungen installieren Sie zuerst jsdom (die Befehle unten).

Prüfungen ausführen (learnrun.js muss im selben Ordner liegen):

npm install jsdom@28.1.0
node --test
learnrun.js herunterladen

Übung 2 von 2

Eine Speisekarte aus einem Template

Die Seite zeigt einen Platzhalter, "Loading the menu...", und hat ein <template id="dish"> für eine Zeile. Entfernen Sie den Platzhalter. Kopieren Sie dann für jedes Gericht, das nicht ausverkauft ist, das Template, füllen Sie .name mit dem Namen und .price mit "€" plus dem Preis mit zwei Nachkommastellen (€4.00) und fügen Sie die Zeile in #menu ein. Das Template selbst muss leer bleiben. Der Starter behält den Platzhalter, ignoriert soldOut und das Template und baut einfache li-Elemente.

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser den JavaScript-Runner herunter (bis zu 0.1 MB) und speichert ihn im Cache. Ihr Code läuft in der eigenen Engine Ihres Browsers und bleibt auf Ihrem Gerät.

Ihre Seite

Ihre Seite läuft in einem abgeschotteten Rahmen. Sie hat keinen Zugriff auf diese Website, und ihr localStorage liegt nur für diesen Lauf im Speicher: Der nächste Lauf beginnt leer.

Führen Sie das Programm aus, um seine Seite hier zu sehen.

Hinweise
  1. Hinweis 1

    Führen Sie die Tests aus: Der Platzhalter ist noch da, Pasta wird gezeigt, und die Zeilen haben kein .name und kein .price.

  2. Hinweis 2

    Entfernen Sie den Platzhalter mit document.querySelector('#placeholder').remove() und überspringen Sie ein ausverkauftes Gericht mit continue.

  3. Hinweis 3

    Für jedes Gericht: const row = document.importNode(template.content, true); füllen Sie row.querySelector('.name') und '.price', dann menu.append(row). dish.price.toFixed(2) ergibt "4.00".

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

const dishes = [
  { name: 'Soup', price: 4, soldOut: false },
  { name: 'Pasta', price: 9, soldOut: true },
  { name: 'Salad', price: 6.5, soldOut: false }
];
const menu = document.querySelector('#menu');
const template = document.querySelector('#dish');

document.querySelector('#placeholder').remove();
for (const dish of dishes) {
  if (dish.soldOut) continue;
  const row = document.importNode(template.content, true);
  row.querySelector('.name').textContent = dish.name;
  row.querySelector('.price').textContent = '€' + dish.price.toFixed(2);
  menu.append(row);
}
Auf dem eigenen Computer ausführen

Installieren Sie ECMAScript 2026 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.js

const dishes = [
  { name: 'Soup', price: 4, soldOut: false },
  { name: 'Pasta', price: 9, soldOut: true },
  { name: 'Salad', price: 6.5, soldOut: false }
];
const menu = document.querySelector('#menu');
const template = document.querySelector('#dish');

// TODO: remove the placeholder; one row from the template per dish that is not sold out.
for (const dish of dishes) {
  const li = document.createElement('li');
  li.textContent = dish.name;
  menu.append(li);
}

main.test.js

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {loadPage} from './learnrun.js';

const texts = (page, selector) => [...page.document.querySelectorAll(selector)].map((el) => el.textContent);

test('Der Platzhalter ist weg', async () => {
  const page = await loadPage();
  assert.equal(page.document.querySelector('#placeholder'), null, 'der Platzhalter ist noch auf der Seite');
});

test('Nur die nicht ausverkauften Gerichte werden gezeigt', async () => {
  const page = await loadPage();
  const names = texts(page, '#menu .name');
  assert.deepEqual(names, ['Soup', 'Salad'], 'die Speisekarte zeigt ' + JSON.stringify(names));
});

test('Jede Zeile zeigt ihren Preis mit zwei Nachkommastellen', async () => {
  const page = await loadPage();
  const prices = texts(page, '#menu .price');
  assert.deepEqual(prices, ['€4.00', '€6.50'], 'die Preise sind ' + JSON.stringify(prices));
});

test('Jede Zeile der Speisekarte ist eine Kopie des Templates', async () => {
  const page = await loadPage();
  const classes = [...page.document.querySelectorAll('#menu li')].map((li) => li.className);
  assert.deepEqual(classes, ['dish', 'dish'], 'die Zeilen haben die Klassen ' + JSON.stringify(classes));
});

test('Das Template selbst bleibt leer', async () => {
  const page = await loadPage();
  const content = page.document.querySelector('#dish').content;
  assert.equal(content.querySelector('.name').textContent, '', 'das Template wurde ausgefüllt: kopieren Sie es und füllen Sie die Kopie');
});

index.html

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Menu</title>
</head>
<body>
  <h1>Today's menu</h1>
  <ul id="menu"><li id="placeholder">Loading the menu...</li></ul>
  <template id="dish"><li class="dish"><span class="name"></span> <span class="price"></span></li></template>
  <script type="module" src="main.js"></script>
</body>
</html>

package.json

{
  "type": "module"
}

package.json sagt Node.js, dass die .js-Dateien Module sind; lassen Sie die Datei im Ordner.

Programm ausführen:

Um die Seite auf Ihrem Computer auszuführen, öffnen Sie index.html über einen lokalen Webserver, nicht als Datei: Browser führen Modul-Skripte von file://-Adressen nicht aus. Für die Prüfungen installieren Sie zuerst jsdom (die Befehle unten).

Prüfungen ausführen (learnrun.js muss im selben Ordner liegen):

npm install jsdom@28.1.0
node --test
learnrun.js herunterladen

Häufige Fehler

Die Template-Kopie erst nach dem Einfügen füllen

const template = document.querySelector('#row');
const copy = document.importNode(template.content, true);
document.querySelector('#list').append(copy);
copy.querySelector('.name').textContent = 'eggs';

Was Node.js ausgibt

TypeError: Cannot set properties of null (setting 'textContent')

Warum, und die Lösung

Die Kopie von template.content ist ein DocumentFragment. Beim Anhängen wandern seine Knoten in die Liste, und das Fragment bleibt leer, also findet copy.querySelector('.name') nichts und gibt null zurück. Füllen Sie zuerst die Kopie und hängen Sie sie dann an. Firefox und Safari formulieren diese Meldung anders.

Text an appendChild übergeben

const list = document.querySelector('#list');
list.appendChild('eggs');

Was Node.js ausgibt

TypeError: Failed to execute 'appendChild' on 'Node': parameter 1 is not of type 'Node'.

Warum, und die Lösung

appendChild, die ältere Methode aus vielen Tutorials, nimmt nur einen Knoten. Ein String muss erst ein Element werden: Erzeugen Sie ein li, setzen Sie seinen textContent auf "eggs" und fügen Sie das ein. append nimmt Strings, aber list.append("eggs") würde nackten Text in die ul setzen, keinen neuen Eintrag.

children mit forEach durchlaufen

const list = document.querySelector('#list');
list.children.forEach((li) => li.remove());

Was Node.js ausgibt

TypeError: list.children.forEach is not a function

Warum, und die Lösung

element.children ist eine HTMLCollection, die length und Indizes hat, aber kein forEach oder map. Um eine Liste zu leeren, rufen Sie list.replaceChildren() ohne Argumente auf. Um die Kinder zu durchlaufen, verteilen Sie sie zuerst mit Spread: [...list.children].forEach(…), oder nehmen Sie for...of.

JavaScript im Browser: die eigene Engine Ihres Browsers in einem abgeschotteten Worker. Syntaxfehler findet acorn 8.18.0, MIT. Lizenz und Quellcode

Abschlussquiz

5 Fragen, ohne Hinweise. Ab 80 % ist die Lektion abgeschlossen.

Erledigen Sie zuerst alle Aufgaben oben, um das Abschlussquiz freizuschalten.

Problem melden

Etwas ist falsch oder unklar? Beschreiben Sie es kurz, dann wird es geprüft und korrigiert.

#

Mindestens 20 Zeichen.

Nur, wenn Sie eine Antwort wünschen.

Kernideen

Erzeugen, füllen, einfügen

document.createElement("li") erzeugt ein neues Element. Es ist noch nicht auf der Seite: Sie füllen es zuerst, mit textContent, classList und setAttribute, und fügen es dann ein. parent.append(child) setzt es hinter das letzte Kind, parent.prepend(child) vor das erste. Beide nehmen mehrere Argumente auf einmal, auch Strings, die zu Text werden. Ein Element kann nur an einer Stelle sein: Ein Element anzuhängen, das schon auf der Seite ist, verschiebt es dorthin, und eine Schleife, die dasselbe Element dreimal anhängt, hinterlässt einen Eintrag. Erzeugen Sie also für jeden Eintrag ein neues Element. Das ältere appendChild aus dem MDN-Tutorial nimmt einen Knoten und keine Strings.

Entfernen und ersetzen

el.remove() nimmt ein Element von der Seite. Die Variable hält es weiter, mit seinem Text, Sie könnten es also wieder einfügen. parent.replaceChildren(a, b) entfernt alle Kinder von parent und setzt a und b an ihre Stelle; ohne Argumente leert es parent. So leeren Sie eine Liste sauber: ohne Schleife, ohne innerHTML = "". Achten Sie auf das Ziel: list.remove() entfernt die ganze Liste, list.replaceChildren() behält die Liste und entfernt ihre Einträge. element.children listet die Kindelemente; wie eine NodeList hat es kein forEach oder map, verteilen Sie es also zuerst mit Spread: [...list.children].

Aus Daten rendern

Halten Sie die Daten in einem Array und schreiben Sie eine Funktion render(), die jeden Eintrag aus dem Array baut und alle mit list.replaceChildren(...items) einsetzt. Ändern sich die Daten, ändern Sie das Array und rufen render() erneut auf; die Seite passt dann zu den Daten, nichts wird doppelt. Wer einzelne Knoten von Hand bearbeitet, hat bald Seite und Daten im Widerspruch. Zeilen mit mehr Markup schreiben Sie einmal in ein <template>, das die Seite nicht anzeigt, und kopieren es mit document.importNode(template.content, true). MDN empfiehlt importNode, damit Custom Elements in der Kopie die Definitionen des aktuellen Dokuments nutzen. Füllen Sie die Kopie, bevor Sie sie einfügen. Ein DocumentFragment sammelt Knoten abseits der Seite und fügt sie in einem Schritt ein.

Quellen

Zuletzt geprüft am 4. Oktober 2026