27 DOM-Struktur und Navigation

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.

27.1 Grundlegende DOM-Architektur

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:

  1. Document-Knoten: Repräsentiert das gesamte Dokument (Wurzelknoten)
  2. Element-Knoten: Repräsentiert HTML-Elemente wie <div>, <p>, etc.
  3. Attribut-Knoten: Repräsentiert Attribute von Elementen
  4. Text-Knoten: Repräsentiert Textinhalte innerhalb von Elementen
  5. Kommentar-Knoten: Repräsentiert HTML-Kommentare
// 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');

27.2 DOM-Schnittstellen und -Objekte

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);  // true

Das DOM bietet verschiedene Eigenschaften und Methoden, um durch den Baum zu navigieren:

27.3.1 Eltern-Kind-Beziehungen

// 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

27.3.2 Geschwister-Beziehungen

// 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-Element

Wichtig: 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.

27.4 DOM-Selektoren und Elementsuche

Moderne Webentwicklung nutzt in der Regel Selektoren anstelle manueller Navigation durch den DOM-Baum:

27.4.1 Elementsuche nach ID, Klasse oder Tag

// 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

27.4.2 CSS-Selektor-basierte Suche

// 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 Containers

27.5 NodeList vs. HTMLCollection

Bei der DOM-Navigation ist es wichtig, die Unterschiede zwischen den zurückgegebenen Sammlungstypen zu verstehen:

// 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:

  1. Statisch vs. Live: NodeList ist in der Regel statisch (bei querySelectorAll), während HTMLCollection immer live ist und DOM-Änderungen widerspiegelt
  2. Methoden: NodeList bietet Methoden wie forEach, die in HTMLCollection fehlen
  3. Inhalt: NodeList kann verschiedene Knotentypen enthalten, HTMLCollection nur Element-Knoten

27.6 Traversieren des DOM-Baums

Fü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());
  }
});

27.7 Performanceaspekte bei der DOM-Navigation

Die DOM-Navigation kann auf großen Seiten rechenintensiv werden. Einige Optimierungstechniken:

  1. Caching von DOM-Referenzen: Häufig verwendete Elemente in Variablen speichern
  2. Vermeidung von Reflows: DOM-Anfragen in Batches verarbeiten
  3. Delegation: Event-Delegation nutzen, statt viele einzelne Listener anzufügen
  4. Verwendung spezifischer Selektoren: Präzise Selektoren sind effizienter
// 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;

27.8 Document Fragments

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);

27.9 Shadow DOM und Kapselung

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.