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 odernull, wenn der Knoten keinen Elternteil hat. - Die
childNodes-Eigenschaft gibt eineNodeListder Kindknoten zurück. DiefirstChildundlastChild-Eigenschaften geben das erste und letzte Element dieser Liste zurück, beziehungsweise, odernull, 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 gibttruezurück, wenn es Kindknoten gibt, d.h. es handelt sich nicht um ein Blatt. - Die
previousSiblingundnextSibling-Eigenschaften geben den vorherigen und nächsten Geschwisterknoten zurück, beziehungsweise, odernull, wenn ein solches Geschwister nicht existiert. - Die
contains()-Methode gibttruezurü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:
<!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:
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 diedoctype-Eigenschaft desDocument-Knotens zugänglich. - Ein optionaler
Element-Knoten, der das Wurzelelement darstellt. Für HTML-Dokumente (wie in unserem Fall) ist dies typischerweise dasHTMLHtmlElement. Für SVG-Dokumente ist dies typischerweise dasSVGSVGElement. Dieser Knoten ist auch über diedocumentElement-Eigenschaft desDocument-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
URLunddocumentURI-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:
<!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:
<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:
<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):
<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:
<!-- 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-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:
<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 dasAttran einem angegebenen Index zurückgibt. - Die
getNamedItem()-Methode, die dasAttrmit 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:
element.getAttribute(name)entsprichtelement.attributes.getNamedItem(name).value, wenn das Attribut existiert.element.getAttributeNode(name)entsprichtelement.attributes.getNamedItem(name).element.hasAttribute(name)entsprichtelement.attributes.getNamedItem(name) !== null.element.getAttributeNames()gibt ein Array aller Attributnamen zurück.element.hasAttributes()entsprichtelement.attributes.length > 0.
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 einElementist, odernull, wenn der Elternteil keinElementist (zum Beispiel, wenn der Elternteil einDocumentist). Dies steht im Gegensatz zuparentNode, das den Elternknoten unabhängig von seinem Typ zurückgibt. - Für
Document,DocumentFragmentundElementgibt diechildren-Eigenschaft einHTMLCollectionnur der Kind-Element-Knoten zurück. Dies steht im Gegensatz zuchildNodes, die alle Kindknoten zurückgibt. DiefirstElementChildundlastElementChild-Eigenschaften geben das erste und letzte Element dieser Sammlung zurück, beziehungsweise, odernull, wenn keine Kindelemente vorhanden sind. DiechildElementCount-Eigenschaft gibt die Anzahl der Kindelemente zurück. - Für
ElementundCharacterDatageben diepreviousElementSiblingundnextElementSibling-Eigenschaften den vorherigen und nächsten Geschwisterknoten zurück, der einElementist, odernull, wenn ein solches Geschwister nicht existiert. Dies steht im Gegensatz zupreviousSiblingundnextSibling, 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
Documentgibt es keine Daten, es müssen daher nur die Kindknoten verglichen werden. - Für
DocumentTypemüssen die Eigenschaftenname,publicIdundsystemIdverglichen werden. - Für
Elementmüssen dertagName(genauer gesagt, dasnamespaceURI,prefixundlocalName, die wir im XML-Namespaces-Leitfaden vorstellen werden) und die Attribute verglichen werden. - Für
Attrmüssen diename(genauer gesagt, dasnamespaceURI,prefix,localName) undvalue-Eigenschaften verglichen werden. - Für alle
CharacterData-Knoten (Text,CDATASection,CommentundProcessingInstruction) muss diedata-Eigenschaft verglichen werden. FürProcessingInstructionmuss auch dietarget-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
0zurück, wennaundbderselbe 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, wennabin der Attributliste vorausgeht, oderNode.DOCUMENT_POSITION_FOLLOWING | Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC(36), wennabfolgt. 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) oderNode.DOCUMENT_POSITION_DISCONNECTED | Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC | Node.DOCUMENT_POSITION_FOLLOWING(37) zurück. Welche zurückgegeben wird, ist implementierungsspezifisch. - Wenn
aein Vorfahre vonbist (einschließlich wennbein Attribut vonaist), gibt sieNode.DOCUMENT_POSITION_CONTAINS | Node.DOCUMENT_POSITION_PRECEDING(10) zurück. - Wenn
aein Nachfahre vonbist (einschließlich wennaein Attribut vonbist), gibt sieNode.DOCUMENT_POSITION_CONTAINED_BY | Node.DOCUMENT_POSITION_FOLLOWING(20) zurück. - Wenn
abin Baumreihenfolge vorausgeht, gibt sieNode.DOCUMENT_POSITION_PRECEDING(2) zurück. - Wenn
abin Baumreihenfolge folgt, gibt sieNode.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:
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.
- Alle Knoten im DOM implementieren das
Node-Interface. - Um im DOM-Baum zu navigieren:
parentNode,childNodes,firstChild/lastChild,hasChildNodes(),getRootNode(),previousSibling/nextSibling. - Um im Elementbaum zu navigieren:
parentElement,children,firstElementChild/lastElementChild,childElementCount,previousElementSibling/nextElementSibling. - Die
nodeType-Eigenschaft zeigt den Typ des Knotens an. DienodeName,nodeValueundtextContent-Eigenschaften liefern die Daten des Knotens. - Der
Document-Knoten und seine zwei wichtigen Kinder:doctypeunddocumentElement. - Der
DocumentType-Knoten und seine drei Eigenschaften:name,publicId, undsystemId. - Der
Element-Knoten und seine Eigenschaften:tagName,attributes. - Der
Attr-Knoten und seine Eigenschaften:nameundvalue. - Das
CharacterData-Interface und seine Eigenschaft:data. - Die vier
CharacterData-Unterklassen:Text,CDATASection,Comment, undProcessingInstruction.ProcessingInstructionhat auch dietarget-Eigenschaft. - Die verschiedenen Möglichkeiten, mit Attributen zu arbeiten, einschließlich der
id,className, undclassList-Eigenschaften. - Die drei Methoden, um Knoten zu vergleichen:
isEqualNode(),isSameNode(), undcompareDocumentPosition().