Dieser Inhalt wurde automatisch aus dem Englischen übersetzt, und kann Fehler enthalten. Erfahre mehr über dieses Experiment.

View in English Always switch to English

Anatomie des DOM

Das DOM stellt ein XML- oder HTML-Dokument als Baum dar. Diese Seite führt die grundlegende Struktur des DOM-Baums ein sowie die verschiedenen Eigenschaften und Methoden, die zu dessen Navigation verwendet werden.

Zunächst müssen wir einige Konzepte im Zusammenhang mit Bäumen einführen. Ein Baum ist eine Datenstruktur, die aus Knoten besteht. Jeder Knoten hält Daten. Die Knoten sind hierarchisch organisiert - jeder Knoten hat einen einzelnen Elternknoten (außer dem Wurzelknoten, der keinen Elternteil hat), und eine geordnete Liste von null oder mehr Kindknoten. Nun können wir Folgendes definieren:

  • Ein Knoten ohne Elternteil wird als Wurzel des Baums bezeichnet.
  • Ein Knoten ohne Kinder wird als Blatt bezeichnet.
  • Knoten, die denselben Elternteil teilen, werden als Geschwister bezeichnet. Geschwister gehören zur gleichen Kindknotenliste ihres Elternteils und haben daher eine gut definierte Reihenfolge.
  • Wenn wir von Knoten A zu Knoten B gelangen können, indem wir wiederholt den Elternlinks folgen, dann ist A ein Nachfahre von B und B ein Vorfahre von A.
  • Knoten in einem Baum werden in Baumreihenfolge aufgeführt, indem zuerst der Knoten selbst und dann rekursiv jeder seiner Kindknoten in der Reihe (Preorder, Tiefen-basiertes Traversieren) aufgeführt wird.

Hier sind einige wichtige Eigenschaften von Bäumen:

  • Jeder Knoten ist mit einem eindeutigen Wurzelknoten verbunden.
  • Wenn Knoten A der Elternknoten von Knoten B ist, dann ist Knoten B ein Kind von Knoten A.
  • Zyklen sind nicht erlaubt: Kein Knoten kann ein Vorfahre oder Nachfahre von sich selbst sein.

Das Node-Interface und seine Unterklassen

Alle Knoten im DOM werden durch Objekte repräsentiert, die das Node-Interface implementieren. Das Node-Interface verkörpert viele der zuvor definierten Konzepte:

  • Die parentNode-Eigenschaft gibt den Elternknoten zurück oder null, wenn der Knoten keinen Elternteil hat.
  • Die childNodes-Eigenschaft gibt eine NodeList der Kindknoten zurück. Die firstChild und lastChild-Eigenschaften geben das erste und letzte Element dieser Liste zurück, beziehungsweise, oder null, wenn keine Kinder existieren.
  • Die getRootNode()-Methode gibt die Wurzel des Baums zurück, der den Knoten enthält, indem wiederholt die Elternlinks verfolgt werden.
  • Die hasChildNodes()-Methode gibt true zurück, wenn es Kindknoten gibt, d.h. es handelt sich nicht um ein Blatt.
  • Die previousSibling und nextSibling-Eigenschaften geben den vorherigen und nächsten Geschwisterknoten zurück, beziehungsweise, oder null, wenn ein solches Geschwister nicht existiert.
  • Die contains()-Methode gibt true zurück, wenn ein bestimmter Knoten ein Nachfahre des Knotens ist.
  • Die compareDocumentPosition()-Methode vergleicht zwei Knoten in Baumreihenfolge. Der Abschnitt Vergleichen von Knoten erörtert diese Methode im Detail.

Man arbeitet selten mit reinen Node-Objekten - stattdessen implementieren alle Objekte im DOM eines der Interfaces, die von Node erben und zusätzliche Semantiken im Dokument repräsentieren. Die Knotentypen beschränken, welche Daten sie enthalten und welche Kindertypen gültig sind. Betrachten Sie, wie das folgende HTML-Dokument im DOM dargestellt wird:

html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>Document</title>
  </head>
  <body>
    <h1>Hello, world!</h1>
    <p>This is a paragraph.</p>
  </body>
</html>

Es erzeugt den folgenden DOM-Baum:

Der DOM-Baum des vorherigen HTML-Dokuments

Die Wurzel dieses DOM-Baums ist ein Document-Knoten, der das gesamte Dokument repräsentiert. Dieser Knoten ist global als die document-Variable verfügbar. Dieser Knoten hat zwei wichtige Kindknoten:

  • Ein optionaler DocumentType-Knoten, der die doctype-Deklaration darstellt. In unserem Fall gibt es einen. Dieser Knoten ist auch über die doctype-Eigenschaft des Document-Knotens zugänglich.
  • Ein optionaler Element-Knoten, der das Wurzelelement darstellt. Für HTML-Dokumente (wie in unserem Fall) ist dies typischerweise das HTMLHtmlElement. Für SVG-Dokumente ist dies typischerweise das SVGSVGElement. Dieser Knoten ist auch über die documentElement-Eigenschaft des Document-Knotens zugänglich.

Der DocumentType-Knoten ist immer ein Blattknoten. Der Element-Knoten ist der Ort, an dem der größte Teil des Dokumenteninhalts repräsentiert wird. Jedes darunterliegende Element, wie <head>, <body>, und <p>, wird auch durch einen Element-Knoten repräsentiert. Tatsächlich ist jedes eine Unterklasse von Element, die spezifisch für einen Tag-Namen ist, definiert in der HTML-Spezifikation, wie HTMLHeadElement und HTMLBodyElement, mit zusätzlichen Eigenschaften und Methoden zur Darstellung der Semantik dieses Elements, aber hier konzentrieren wir uns auf die allgemeinen Verhaltensweisen des DOM. Die Element-Knoten können andere Element-Knoten als Kinder haben, die verschachtelte Elemente darstellen. Zum Beispiel hat das <head>-Element drei Kinder: zwei <meta>-Elemente und ein <title>-Element. Zusätzlich können Elemente auch Text-Knoten und CDATASection-Knoten als Kinder haben, um Textinhalt darzustellen. Zum Beispiel hat das <p>-Element einen einzigen Kindknoten, einen Text-Knoten, der den String "This is a paragraph." enthält. Text-Knoten und CDATASection-Knoten sind immer Blattknoten.

Alle Knoten, die Kinder haben können (Document, DocumentFragment und Element) erlauben zwei Arten von Kindern: Comment und ProcessingInstruction-Knoten. Diese Knoten sind immer Blattknoten.

Jedes Element kann neben Kindknoten auch Attribute haben, die als Attr-Knoten repräsentiert werden. Attr erweitert das Node-Interface, ist aber nicht Teil der Hauptbaustruktur, da sie nicht das Kind eines Knotens sind und der Elternknoten null ist. Stattdessen sind sie in einer separaten Named-Node-Map gespeichert, die über die attributes-Eigenschaft des Element-Knotens zugänglich ist.

Das Node-Interface definiert eine nodeType-Eigenschaft, die den Typ des Knotens anzeigt. Zusammenfassend haben wir die folgenden Knotentypen eingeführt:

Knotentyp nodeType-Wert Gültige Kinder (neben Comment und ProcessingInstruction)
Document Node.DOCUMENT_NODE (9) DocumentType, Element
DocumentType Node.DOCUMENT_TYPE_NODE (10) Keine
Element Node.ELEMENT_NODE (1) Element, Text, CDATASection
Text Node.TEXT_NODE (3) Keine
CDATASection Node.CDATA_SECTION_NODE (4) Keine
Comment Node.COMMENT_NODE (8) Keine
ProcessingInstruction Node.PROCESSING_INSTRUCTION_NODE (7) Keine
Attr Node.ATTRIBUTE_NODE (2) Keine

Hinweis: Sie bemerken vielleicht, dass wir hier einige Knotentypen ausgelassen haben. Die Werte Node.ENTITY_REFERENCE_NODE (5), Node.ENTITY_NODE (6) und Node.NOTATION_NODE (12) werden nicht mehr verwendet, während der Wert Node.DOCUMENT_FRAGMENT_NODE (11) im Erstellen und aktualisieren des DOM-Baums eingeführt wird.

Daten jedes Knotens

Jeder Knotentyp hat seine eigene Art, die Daten darzustellen, die er hält. Das Node-Interface selbst definiert drei Eigenschaften in Bezug auf Daten, zusammengefasst in der folgenden Tabelle:

Knotentyp nodeName nodeValue textContent
Document "#document" null null
DocumentType Sein name (z.B. "html") null null
Element Sein tagName (z.B. "HTML", "BODY") null Verkettung aller seiner Textknotennachfahren in Baumreihenfolge
Text "#text" Sein data Sein data
CDATASection "#cdata-section" Sein data Sein data
Comment "#comment" Sein data Sein data
ProcessingInstruction Sein target Sein data Sein data
Attr Sein name Sein value Sein value

Document

Der Document-Knoten selbst hält keine Daten, daher sind seine nodeValue und textContent immer null. Sein nodeName ist immer "#document".

Der Document-Knoten definiert jedoch einige Metadaten über das Dokument, die aus der Umgebung stammen (zum Beispiel die HTTP-Antwort, die das Dokument bereitgestellt hat):

  • Die URL und documentURI-Eigenschaften geben die URL des Dokuments zurück.
  • Die characterSet-Eigenschaft gibt die im Dokument verwendete Zeichencodierung zurück, wie "UTF-8".
  • Die compatMode-Eigenschaft gibt den Darstellungsmodus des Dokuments zurück, entweder "CSS1Compat" (Standardmodus) oder "BackCompat" (Quirks-Modus).
  • Die contentType-Eigenschaft gibt den Medientyp des Dokuments zurück, beispielsweise "text/html" für HTML-Dokumente.

DocumentType

Ein DocumentType im Dokument sieht folgendermaßen aus:

xml
<!doctype name PUBLIC "publicId" "systemId">

Es gibt drei Teile, die Sie angeben können und die den drei Eigenschaften des DocumentType-Knotens entsprechen: name, publicId, und systemId. Für HTML-Dokumente ist der Doctype immer <!doctype html>, daher ist name "html" und sowohl publicId als auch systemId sind leere Zeichenketten.

Element

Ein Element im Dokument sieht folgendermaßen aus:

html
<p class="note" id="intro">This is a paragraph.</p>

Zusätzlich zu den Inhalten gibt es zwei Teile, die Sie angeben können: den Tag-Namen und die Attribute. Der Tag-Name entspricht der tagName-Eigenschaft des Element-Knotens, die in diesem Fall "P" ist (beachten Sie, dass er bei HTML-Elementen immer in Großbuchstaben steht). Die Attribute entsprechen den Attr-Knoten, die in der attributes-Eigenschaft des Element-Knotens gespeichert sind. Wir werden Attribute ausführlicher im Abschnitt Element und seine Attribute diskutieren.

Der Element-Knoten selbst hält keine Daten, daher ist seine nodeValue immer null. Sein textContent ist die Verkettung aller seiner Textknotennachfahren in Baumreihenfolge, die in diesem Fall "This is a paragraph." ist. Für das folgende Element:

html
<div>Hello, <span>world</span>!</div>

Das textContent ist "Hello, world!", das die Textknoten "Hello, ", den Textknoten "world" innerhalb des <span>-Elements und den Textknoten "!" verkettet.

CharacterData

Text, CDATASection, Comment, und ProcessingInstruction erben alle vom CharacterData-Interface, das eine Unterklasse von Node ist. Das CharacterData-Interface definiert eine einzelne Eigenschaft, data, die den Textinhalt des Knotens enthält. Die data-Eigenschaft wird auch zur Implementierung der nodeValue- und textContent-Eigenschaften dieser Knoten verwendet.

Für Text und CDATASection enthält die data-Eigenschaft den Textinhalt des Knotens. Im folgenden Dokument (beachten Sie, dass wir ein SVG-Dokument verwenden, da HTML keine CDATA-Abschnitte zulässt):

svg
<text>Some text</text>
<style><![CDATA[h1 { color: red; }]]></style>

Der Textknoten innerhalb des <text>-Elements hat "Some text" als data, und der CDATA-Abschnitt innerhalb des <style>-Elements hat "h1 { color: red; }" als data.

Für Comment enthält die data-Eigenschaft den Inhalt des Kommentars, beginnend nach dem <!-- und endend vor dem -->. Zum Beispiel im folgenden Dokument:

html
<!-- This is a comment -->

Der Kommentarknoten hat " This is a comment " als data.

Für ProcessingInstruction enthält die data-Eigenschaft den Inhalt der Verarbeitungserklärung, beginnend nach dem Ziel und endend vor dem ?>. Zum Beispiel im folgenden Dokument:

xml
<?xml-stylesheet type="text/xsl" href="style.xsl"?>

Der Verarbeitungserklärungsknoten hat 'type="text/xsl" href="style.xsl"' als data und "xml-stylesheet" als sein target.

Zusätzlich definiert das CharacterData-Interface die length-Eigenschaft, die die Länge der data-Zeichenkette zurückgibt, und die substringData()-Methode, die eine Teilzeichenkette der data zurückgibt.

Attr

Für das folgende Element:

html
<p class="note" id="intro">This is a paragraph.</p>

Das <p>-Element hat zwei Attribute, die durch zwei Attr-Knoten repräsentiert werden. Jedes Attribut besteht aus einem Namen und einem Wert, die den name- und value-Eigenschaften entsprechen. Das erste Attribut hat "class" als name und "note" als value, während das zweite Attribut "id" als name und "intro" als value hat.

Element und seine Attribute

Wie schon erwähnt, werden die Attribute eines Element-Knotens durch Attr-Knoten repräsentiert, die in einer separaten Named-Node-Map gespeichert sind, zugänglich über die attributes-Eigenschaft des Element-Knotens. Diese NamedNodeMap-Schnittstelle definiert drei wichtige Eigenschaften:

  • length, das die Anzahl der Attribute zurückgibt.
  • Die item()-Methode, die das Attr an einem angegebenen Index zurückgibt.
  • Die getNamedItem()-Methode, die das Attr mit einem angegebenen Namen zurückgibt.

Das Element-Interface definiert auch mehrere Methoden, um direkt mit Attributen zu arbeiten, ohne auf die Named-Node-Map zugreifen zu müssen:

Sie können auch über die ownerElement-Eigenschaft des Attr-Knotens auf das besitzende Element eines Attributs zugreifen.

Es gibt zwei spezielle Attribute, id und class, die eigene Eigenschaften im Element-Interface haben: id und className, die den Wert des entsprechenden Attributs reflektieren. Darüber hinaus gibt die classList-Eigenschaft eine DOMTokenList zurück, die die Liste der Klassen im class-Attribut repräsentiert.

Arbeiten mit dem Elementbaum

Da Element-Knoten das Rückgrat der Dokumentenstruktur bilden, können Sie speziell die Elementknoten durchlaufen und andere Knoten (wie Text und Comment) überspringen.

  • Für alle Knoten gibt die parentElement-Eigenschaft den Elternknoten zurück, wenn es ein Element ist, oder null, wenn der Elternteil kein Element ist (zum Beispiel, wenn der Elternteil ein Document ist). Dies steht im Gegensatz zu parentNode, das den Elternknoten unabhängig von seinem Typ zurückgibt.
  • Für Document, DocumentFragment und Element gibt die children-Eigenschaft ein HTMLCollection nur der Kind-Element-Knoten zurück. Dies steht im Gegensatz zu childNodes, die alle Kindknoten zurückgibt. Die firstElementChild und lastElementChild-Eigenschaften geben das erste und letzte Element dieser Sammlung zurück, beziehungsweise, oder null, wenn keine Kindelemente vorhanden sind. Die childElementCount-Eigenschaft gibt die Anzahl der Kindelemente zurück.
  • Für Element und CharacterData geben die previousElementSibling und nextElementSibling-Eigenschaften den vorherigen und nächsten Geschwisterknoten zurück, der ein Element ist, oder null, wenn ein solches Geschwister nicht existiert. Dies steht im Gegensatz zu previousSibling und nextSibling, die jeden beliebigen Typ von Geschwisterknoten zurückgeben können.

Vergleichen von Knoten

Es gibt drei wichtige Methoden, die Knoten vergleichen: isEqualNode(), isSameNode(), compareDocumentPosition().

Die Methode isSameNode() ist veraltet. Jetzt verhält sie sich wie der strikte Gleichheitsoperator (===) und gibt true zurück, wenn und nur wenn die beiden Knoten dasselbe Objekt sind.

Die Methode isEqualNode() vergleicht zwei Knoten strukturell. Zwei Knoten werden als gleich angesehen, wenn sie denselben Typ, dieselben Daten haben und ihre Kindknoten an jedem Index ebenfalls gleich sind. Im Abschnitt Daten jedes Knotens haben wir bereits die relevanten Daten für jeden Knotentyp definiert:

  • Für Document gibt es keine Daten, es müssen daher nur die Kindknoten verglichen werden.
  • Für DocumentType müssen die Eigenschaften name, publicId und systemId verglichen werden.
  • Für Element müssen der tagName (genauer gesagt, das namespaceURI, prefix und localName, die wir im XML-Namespaces-Leitfaden vorstellen werden) und die Attribute verglichen werden.
  • Für Attr müssen die name (genauer gesagt, das namespaceURI, prefix, localName) und value-Eigenschaften verglichen werden.
  • Für alle CharacterData-Knoten (Text, CDATASection, Comment und ProcessingInstruction) muss die data-Eigenschaft verglichen werden. Für ProcessingInstruction muss auch die target-Eigenschaft verglichen werden.

Die a.compareDocumentPosition(b)-Methode vergleicht zwei Knoten in Baumreihenfolge. Sie gibt eine Bitmaske zurück, die ihre relativen Positionen anzeigt. Die möglichen Fälle sind:

  • Gibt 0 zurück, wenn a und b derselbe Knoten sind.
  • Wenn die beiden Knoten beide Attribute desselben Elementknotens sind, gibt sie Node.DOCUMENT_POSITION_PRECEDING | Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC (34) zurück, wenn a b in der Attributliste vorausgeht, oder Node.DOCUMENT_POSITION_FOLLOWING | Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC (36), wenn a b folgt. Wenn einer der Knoten ein Attribut ist, wird das besitzende Element für weitere Vergleiche verwendet.
  • Wenn die beiden Knoten nicht denselben Wurzelknoten haben, gibt sie entweder Node.DOCUMENT_POSITION_DISCONNECTED | Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC | Node.DOCUMENT_POSITION_PRECEDING (35) oder Node.DOCUMENT_POSITION_DISCONNECTED | Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC | Node.DOCUMENT_POSITION_FOLLOWING (37) zurück. Welche zurückgegeben wird, ist implementierungsspezifisch.
  • Wenn a ein Vorfahre von b ist (einschließlich wenn b ein Attribut von a ist), gibt sie Node.DOCUMENT_POSITION_CONTAINS | Node.DOCUMENT_POSITION_PRECEDING (10) zurück.
  • Wenn a ein Nachfahre von b ist (einschließlich wenn a ein Attribut von b ist), gibt sie Node.DOCUMENT_POSITION_CONTAINED_BY | Node.DOCUMENT_POSITION_FOLLOWING (20) zurück.
  • Wenn a b in Baumreihenfolge vorausgeht, gibt sie Node.DOCUMENT_POSITION_PRECEDING (2) zurück.
  • Wenn a b in Baumreihenfolge folgt, gibt sie Node.DOCUMENT_POSITION_FOLLOWING (4) zurück.

Bitmaskenwerte werden verwendet, sodass Sie eine bitweise UND-Operation verwenden können, um auf spezifische Beziehungen zu prüfen. Um beispielsweise zu überprüfen, ob a b vorausgeht, können Sie folgendes tun:

js
if (a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_PRECEDING) {
  // a precedes b
}

Dies berücksichtigt die Fälle, in denen a und b Attribute desselben Elements sind, a ein Vorfahre von b ist und a b in Baumreihenfolge vorausgeht.

Zusammenfassung

Hier sind alle Funktionen, die wir bisher vorgestellt haben. Es gibt viele, aber sie sind in verschiedenen Szenarien alle nützlich.