Das Document Object Model (DOM) bildet die Brücke zwischen HTML-Inhalten und JavaScript. Es stellt eine strukturierte Repräsentation des HTML-Dokuments dar und ermöglicht die dynamische Manipulation von Webseiteninhalten. Ein fundiertes Verständnis der DOM-Struktur und der Navigationsmöglichkeiten ist für jede Art der JavaScript-basierten Webentwicklung unverzichtbar.
Das DOM repräsentiert ein HTML-Dokument als hierarchische
Baumstruktur, in der jedes HTML-Element, jedes Attribut und jeder
Textinhalt als ein Knoten (Node) dargestellt wird. Diese Baumstruktur
beginnt mit dem document-Objekt als Wurzel.
Die wichtigsten Knotentypen im DOM sind:
<div>, <p>, etc.// Beispiel: Zugriff auf verschiedene Knotentypen
// Element-Knoten
const bodyElement = document.body;
// Text-Knoten
const paragraph = document.querySelector('p');
const textNode = paragraph.firstChild;
// Attribut-Knoten
const linkElement = document.querySelector('a');
const hrefAttribute = linkElement.getAttributeNode('href');Das DOM folgt einer objektorientierten Struktur mit verschiedenen Schnittstellen, die in einer Vererbungshierarchie organisiert sind:
// Beispiel: Arbeiten mit DOM-Schnittstellen
const element = document.querySelector('div');
// Überprüfen von Vererbungsbeziehungen
console.log(element instanceof Node); // true
console.log(element instanceof Element); // true
console.log(element instanceof HTMLElement); // true
console.log(element instanceof HTMLDivElement); // trueDas DOM bietet verschiedene Eigenschaften und Methoden, um durch den Baum zu navigieren:
// Beispiel: Eltern-Kind-Navigation
const parent = element.parentNode; // Elternknoten
const parentElement = element.parentElement; // Elternknoten (nur Element)
const children = element.childNodes; // Alle Kindknoten (NodeList)
const childElements = element.children; // Nur Kind-Elemente (HTMLCollection)
const firstChild = element.firstChild; // Erster Kindknoten (inkl. Text, Kommentare)
const firstElementChild = element.firstElementChild; // Erstes Kind-Element
const lastChild = element.lastChild; // Letzter Kindknoten
const lastElementChild = element.lastElementChild; // Letztes Kind-Element// Beispiel: Navigation zwischen Geschwistern
const nextSibling = element.nextSibling; // Nächster Geschwisterknoten
const nextElementSibling = element.nextElementSibling; // Nächstes Geschwister-Element
const previousSibling = element.previousSibling; // Vorheriger Geschwisterknoten
const previousElementSibling = element.previousElementSibling; // Vorheriges Geschwister-ElementWichtig: Die Node-basierten Eigenschaften (ohne
“Element” im Namen) berücksichtigen alle Knotentypen, einschließlich
Textknoten und Kommentare, während die Element-basierten
Eigenschaften nur Element-Knoten zurückgeben.
Moderne Webentwicklung nutzt in der Regel Selektoren anstelle manueller Navigation durch den DOM-Baum:
// Beispiel: Traditionelle DOM-Selektoren
const elementById = document.getElementById('main'); // Ein Element nach ID
const elementsByClass = document.getElementsByClassName('item'); // HTMLCollection
const elementsByTag = document.getElementsByTagName('div'); // HTMLCollection// Beispiel: Moderne CSS-Selector-basierte DOM-Suche
const element = document.querySelector('.container > .item'); // Erstes passendes Element
const elements = document.querySelectorAll('ul li'); // Alle passenden Elemente (NodeList)
// Kontext-bezogene Selektion (Scope)
const container = document.querySelector('.container');
const itemsInContainer = container.querySelectorAll('.item'); // Nur Elemente innerhalb des ContainersBei der DOM-Navigation ist es wichtig, die Unterschiede zwischen den zurückgegebenen Sammlungstypen zu verstehen:
querySelectorAll() und childNodes
zurückgegebengetElementsByClassName() und children
zurückgegeben// Beispiel: Unterschiede bei der Iteration
const nodeList = document.querySelectorAll('p');
const htmlCollection = document.getElementsByTagName('p');
// NodeList unterstützt forEach direkt
nodeList.forEach(node => console.log(node.textContent));
// HTMLCollection erfordert Konvertierung oder traditionelle Schleife
Array.from(htmlCollection).forEach(element => console.log(element.textContent));
// oder
for (let i = 0; i < htmlCollection.length; i++) {
console.log(htmlCollection[i].textContent);
}Wesentliche Unterschiede:
querySelectorAll), während HTMLCollection
immer live ist und DOM-Änderungen widerspiegeltforEach, die in HTMLCollection fehlenFür komplexe Durchläufe durch den DOM-Baum können rekursive Funktionen oder iterative Ansätze verwendet werden:
// Beispiel: Rekursives Traversieren des DOM-Baums
function traverseDOM(node, callback) {
// Aktuellen Knoten verarbeiten
callback(node);
// Durch alle Kinder rekursiv gehen
const children = node.childNodes;
for (let i = 0; i < children.length; i++) {
traverseDOM(children[i], callback);
}
}
// Verwendung
traverseDOM(document.body, node => {
if (node.nodeType === Node.ELEMENT_NODE) {
console.log('Element:', node.tagName);
} else if (node.nodeType === Node.TEXT_NODE && node.textContent.trim()) {
console.log('Text:', node.textContent.trim());
}
});Die DOM-Navigation kann auf großen Seiten rechenintensiv werden. Einige Optimierungstechniken:
// Beispiel: Ineffiziente vs. effiziente DOM-Navigation
// Ineffizient - Wiederholt auf das DOM zugreifen
for (let i = 0; i < 1000; i++) {
document.querySelector('.container').innerHTML += '<span>Item</span>';
}
// Effizient - DOM-Referenz cachen, Batch-Operationen
const container = document.querySelector('.container');
let content = '';
for (let i = 0; i < 1000; i++) {
content += '<span>Item</span>';
}
container.innerHTML = content;Für komplexe DOM-Manipulationen bietet das
DocumentFragment eine leichtgewichtige Lösung zum
Konstruieren von DOM-Strukturen im Speicher, bevor sie ins Live-DOM
eingefügt werden:
// Beispiel: Verwendung von DocumentFragment
const fragment = document.createDocumentFragment();
// Elemente zum Fragment hinzufügen (kein Reflow)
for (let i = 0; i < 100; i++) {
const listItem = document.createElement('li');
listItem.textContent = `Item ${i}`;
fragment.appendChild(listItem);
}
// Fragment einmalig dem DOM hinzufügen (nur ein Reflow)
document.querySelector('ul').appendChild(fragment);Das Shadow DOM erweitert das DOM-Konzept um Kapselung und bietet Isolation für Struktur, Stil und Verhalten:
// Beispiel: Erstellen eines Shadow DOM
const host = document.querySelector('.widget');
const shadowRoot = host.attachShadow({ mode: 'open' });
// Inhalte im Shadow DOM erstellen
shadowRoot.innerHTML = `
<style>
/* Styles sind auf diesen Shadow DOM beschränkt */
p { color: red; }
</style>
<p>Dieser Text ist im Shadow DOM gekapselt</p>
`;
// Navigation im Shadow DOM
const paragraph = shadowRoot.querySelector('p');Das Shadow DOM ist besonders nützlich für die Erstellung von Komponenten und wird intensiv in Web Components verwendet. Es wird im Kapitel “Komponenten-basierte Architektur” näher betrachtet.