Anatomie des DOM
Das DOM stellt ein XML- oder HTML-Dokument als Baum dar. Diese Seite führt in die Grundstruktur des DOM-Baums ein und beschreibt die verschiedenen Eigenschaften und Methoden, die zur Navigation verwendet werden.
Zu Beginn müssen wir einige Konzepte im Zusammenhang mit Bäumen einführen. Ein Baum ist eine Datenstruktur, die aus Knoten besteht. Jeder Knoten enthält einige 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 Baumes bezeichnet.
- Ein Knoten ohne Kinder wird als Blatt bezeichnet.
- Knoten, die denselben Elternteil haben, werden als Geschwister bezeichnet. Geschwister gehören zur selben Kindknotenliste ihres Elternteils und haben somit eine klar definierte Reihenfolge.
- Wenn wir von Knoten A zu Knoten B gelangen können, indem wir wiederholt den Eltern-Knoten verfolgen, ist A ein Nachfahre von B und B ein Vorfahre von A.
- Knoten in einem Baum werden in Baumreihenfolge aufgelistet, indem zuerst der Knoten selbst aufgelistet wird und dann rekursiv jeder seiner Kindknoten in Reihenfolge (Präorder-, Tiefensuche) aufgelistet wird.
Hier sind einige wichtige Eigenschaften von Bäumen:
- Jeder Knoten ist mit einem eindeutigen Wurzelknoten verbunden.
- Wenn Knoten A der Elternteil von Knoten B ist, dann ist Knoten B ein Kind von Knoten A.
- Zyklen sind nicht erlaubt: Kein Knoten kann ein Vorfahre oder Nachfahre seiner selbst sein.
Das Node-Interface und seine Unterklassen
Alle Knoten im DOM werden durch Objekte dargestellt, die das Node Interface implementieren. Das Node-Interface verkörpert viele der zuvor definierten Konzepte:
- Die
parentNodeEigenschaft gibt den Elternknoten zurück odernull, wenn der Knoten keinen Elternteil hat. - Die
childNodesEigenschaft gibt einenNodeListder Kindknoten zurück. DiefirstChildundlastChildEigenschaften geben das erste und letzte Element dieser Liste zurück, odernull, wenn keine Kinder vorhanden sind. - Die
getRootNode()Methode gibt die Wurzel des Baumes zurück, der den Knoten enthält, indem sie wiederholt den Elternknoten verfolgt. - Die
hasChildNodes()Methode gibtwahrzurück, wenn es Kindknoten hat, d. h. es ist kein Blatt. - Die
previousSiblingundnextSiblingEigenschaften geben die vorherigen und nächsten Geschwisterknoten zurück odernull, wenn es kein solches Geschwister gibt. - Die
contains()Methode gibtwahrzurück, wenn ein gegebener Knoten ein Nachfahre des Knotens ist. - Die
compareDocumentPosition()Methode vergleicht zwei Knoten nach Baumreihenfolge. Der Abschnitt Vergleichen von Knoten bespricht diese Methode im Detail.
Sie arbeiten selten mit einfachen Node-Objekten. Stattdessen implementieren alle Objekte im DOM eines der Interfaces, die von Node erben und zusätzliche Semantik im Dokument darstellen. 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, initial-scale=1.0" />
<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-Baumes ist ein Document Knoten, der das gesamte Dokument repräsentiert. Dieser Knoten wird global als die document Variable exponiert. Dieser Knoten hat zwei wichtige Kindknoten:
- Einen optionalen
DocumentTypeKnoten, der die doctype Deklaration darstellt. In unserem Fall gibt es einen. Dieser Knoten ist auch über diedoctypeEigenschaft desDocument-Knotens zugänglich. - Einen optionalen
ElementKnoten, 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 diedocumentElementEigenschaft desDocument-Knotens zugänglich.
Der DocumentType-Knoten ist immer ein Blattknoten. Der Element-Knoten ist dort, wo der Hauptinhalt des Dokuments dargestellt wird. Jedes darunterliegende Element, wie zum Beispiel <head>, <body> und <p>, wird ebenfalls durch einen Element-Knoten repräsentiert. Tatsächlich ist jeder eine Unterklasse von Element, die spezifisch für diesen Tag-Namen ist, wie im HTML-Spezifikation definiert, wie HTMLHeadElement und HTMLBodyElement, mit zusätzlichen Eigenschaften und Methoden, die die Semantik dieses Elements darstellen, aber hier konzentrieren wir uns auf die gemeinsamen 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, die Textinhalte darstellen. Zum Beispiel hat das <p>-Element ein einzelnes Kind, einen Text-Knoten, der die Zeichenkette "Dies ist ein Absatz." 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, zusätzlich zu den Kindknoten, auch Attribute haben, die als Attr Knoten dargestellt werden. Attr erweitert das Node-Interface, aber sie sind nicht Teil der Hauptbaumstruktur, da sie kein Kind eines Knotens sind und ihr Elternknoten null ist. Stattdessen werden sie in einem separaten benannten Knoten-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 angibt. Zusammengefasst haben wir die folgenden Knotentypen eingeführt:
| Knotentyp | nodeType-Wert |
Gültige Kinder (außer 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 werden bemerken, dass wir hier einige Knotentypen übersprungen haben. Die Node.ENTITY_REFERENCE_NODE (5), Node.ENTITY_NODE (6) und Node.NOTATION_NODE (12) Werte werden nicht mehr verwendet, während der Node.DOCUMENT_FRAGMENT_NODE (11) Wert 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. Die Node-Schnittstelle selbst definiert drei Eigenschaften, die mit Daten zu tun haben, die in der folgenden Tabelle zusammengefasst sind:
| 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 Textknoten-Nachfahren 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 |
Dokument
Der Document-Knoten enthält selbst keine Daten, sodass seine nodeValue und textContent immer null sind. Sein nodeName ist immer "#document".
Das Document definiert einige Metadaten über das Dokument, die aus der Umgebung stammen (zum Beispiel die HTTP-Antwort, die das Dokument ausgeliefert hat):
- Die
URLunddocumentURIEigenschaften geben die URL des Dokuments zurück. - Die
characterSetEigenschaft gibt die im Dokument verwendete Zeichenkodierung zurück, wie zum Beispiel"UTF-8". - Die
compatModeEigenschaft gibt den Darstellungsmodus des Dokuments zurück, entweder"CSS1Compat"(Standardmodus) oder"BackCompat"(Quirks-Modus). - Die
contentTypeEigenschaft gibt den Medientyp des Dokuments zurück, wie zum Beispiel"text/html"für HTML-Dokumente.
DocumentType
Ein DocumentType im Dokument sieht so aus:
<!doctype name PUBLIC "publicId" "systemId">
Es gibt drei Teile, die Sie angeben können, die den drei Eigenschaften des DocumentType-Knotens entsprechen: name, publicId und systemId. Für HTML-Dokumente ist der Doctype immer <!doctype html>, daher ist der name "html" und sowohl publicId als auch systemId sind leere Zeichenfolgen.
Element
Ein Element im Dokument sieht so 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, der in diesem Fall "P" ist (beachten Sie, dass er bei HTML-Elementen immer großgeschrieben ist). Die Attribute entsprechen den Attr-Knoten, die in der attributes Eigenschaft des Element-Knotens gespeichert sind. Wir werden Attribute im Abschnitt Das Element und seine Attribute ausführlicher besprechen.
Der Element-Knoten enthält selbst keine Daten, daher ist seine nodeValue immer null. Sein textContent ist die Verkettung aller seiner Textknoten-Nachfahren in Baumreihenfolge, die in diesem Fall "Dies ist ein Paragraph." ist. Für das folgende Element:
<div>Hello, <span>world</span>!</div>
ist das textContent "Hello, world!", wobei der Textknoten "Hello, ", der Textknoten "world" im <span>-Element und der Textknoten "!" miteinander verknüpft werden.
CharacterData
Text, CDATASection, Comment, und ProcessingInstruction erben alle von der CharacterData Schnittstelle, die eine Unterklasse von Node ist. Die CharacterData Schnittstelle definiert eine einzelne Eigenschaft, data, die den Textinhalt des Knotens enthält. Die data Eigenschaft wird auch verwendet, um die nodeValue und textContent Eigenschaften dieser Knoten zu implementieren.
Für Text und CDATASection hält die data Eigenschaft den Textinhalt des Knotens. Im folgenden Dokument (beachten Sie, dass wir ein SVG-Dokument verwenden, da HTML keine CDATA-Abschnitte erlaubt):
<text>Some text</text>
<style><![CDATA[h1 { color: red; }]]></style>
hat der Textknoten im <text>-Element "Some text" als data, und der CDATA-Abschnitt im <style>-Element hat "h1 { color: red; }" als data.
Für Comment hält die data Eigenschaft den Inhalt des Kommentars, der nach dem <!-- beginnt und vor dem --> endet. Zum Beispiel im folgenden Dokument:
<!-- This is a comment -->
hat der Kommentar-Knoten " Dies ist ein Kommentar " als data.
Für ProcessingInstruction hält die data Eigenschaft den Inhalt der Verarbeitungseinweisung, die nach dem Ziel beginnt und vor dem ?> endet. Zum Beispiel im folgenden Dokument:
<?xml-stylesheet type="text/xsl" href="style.xsl"?>
hat der Verarbeitungseinweisung-Knoten 'type="text/xsl" href="style.xsl"' als data und "xml-stylesheet" als sein target.
Zusätzlich definiert die CharacterData Schnittstelle die length Eigenschaft, die die Länge der data Zeichenfolge zurückgibt, und die substringData() Methode, die einen Unterstring der data zurückgibt.
Attr
Für das folgende Element:
<p class="note" id="intro">This is a paragraph.</p>
hat das <p> Element zwei Attribute, die durch zwei Attr-Knoten dargestellt 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.
Das Element und seine Attribute
Wie bereits erwähnt, werden die Attribute eines Element-Knotens durch Attr-Knoten dargestellt, die in einem separaten benannten Knoten-Map gespeichert werden, das über die attributes Eigenschaft des Element-Knotens zugänglich ist. Diese NamedNodeMap Schnittstelle definiert drei wichtige Eigenschaften:
length, die die Anzahl der Attribute zurückgibt.item()Methode, die dasAttran einem gegebenen Index zurückgibt.getNamedItem()Methode, die dasAttrmit einem bestimmten Namen zurückgibt.
Das Element-Interface definiert außerdem mehrere Methoden, um direkt mit Attributen zu arbeiten, ohne auf den benannten Knoten-Map zugreifen zu müssen:
element.getAttribute(name)ist äquivalent zuelement.attributes.getNamedItem(name).value, wenn das Attribut existiert.element.getAttributeNode(name)ist äquivalent zuelement.attributes.getNamedItem(name).element.hasAttribute(name)ist äquivalent zuelement.attributes.getNamedItem(name) !== null.element.getAttributeNames()gibt ein Array aller Attributnamen zurück.element.hasAttributes()ist äquivalent zuelement.attributes.length > 0.
Sie können auch über die ownerElement Eigenschaft des Attr-Knotens auf das Eigentümer-Element eines Attributs zugreifen.
Es gibt zwei spezielle Attribute, id und class, die ihre eigenen Eigenschaften im Element-Interface haben: id und className, die den Wert des entsprechenden Attributs reflektieren. Zusätzlich gibt die classList Eigenschaft eine DOMTokenList zurück, die die Liste der Klassen im class-Attribut darstellt.
Arbeiten mit dem Elementbaum
Da Element-Knoten das Rückgrat der Dokumentstruktur bilden, können Sie speziell die Elementknoten durchlaufen und andere Knoten (wie Text und Comment) überspringen.
- Für alle Knoten gibt die
parentElementEigenschaft den Elternknoten zurück, wenn es sich um einElementhandelt, 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 diechildrenEigenschaft eineHTMLCollectionvon nur den Kind-Element-Knoten zurück. Dies steht im Gegensatz zuchildNodes, das alle Kindknoten zurückgibt. DiefirstElementChildundlastElementChildEigenschaften geben das erste und letzte Element dieser Sammlung zurück odernull, wenn keine Kind-Elemente vorhanden sind. DiechildElementCountEigenschaft gibt die Anzahl der Kind-Elemente zurück. - Für
ElementundCharacterDatageben diepreviousElementSiblingundnextElementSiblingEigenschaften das vorherige und nächste Geschwisterelement zurück, das einElementist, bzw.null, wenn kein solches Geschwisterelement existiert. Dies steht im Gegensatz zupreviousSiblingundnextSibling, die jeden Typ von Geschwisterknoten zurückgeben können.
Vergleichen von Knoten
Es gibt drei wichtige Methoden, die Knoten vergleichen: isEqualNode(), isSameNode(), compareDocumentPosition().
Die isSameNode() Methode ist veraltet. Jetzt verhält sie sich wie der strikte Gleichheitsoperator (===), indem sie wahr zurückgibt, wenn und nur wenn die beiden Knoten dasselbe Objekt sind.
Die isEqualNode() Methode 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 Daten definiert, die für jeden Knotentyp relevant sind:
- Für
Documentgibt es keine Daten, daher müssen nur die Kindknoten verglichen werden. - Für
DocumentTypemüssen die Eigenschaftenname,publicIdundsystemIdverglichen werden. - Für
ElementmüssentagName(genauer gesagt,namespaceURI,prefixundlocalName; wir werden diese im XML-Namensräume Leitfaden einführen) und die Attribute verglichen werden. - Für
Attrmüssen die Eigenschaftenname(genauer gesagt,namespaceURI,prefixundlocalName; wir werden diese im XML-Namensräume Leitfaden einführen) undvalueverglichen werden. - Für alle
CharacterDataKnoten (Text,CDATASection,CommentundProcessingInstruction) muss diedataEigenschaft verglichen werden. FürProcessingInstructionmuss auch dietargetEigenschaft verglichen werden.
Die a.compareDocumentPosition(b) Methode vergleicht zwei Knoten nach 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 es
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 beiden Knoten ein Attribut ist, wird das Eigentümerelement für weitere Vergleiche verwendet. - Wenn die beiden Knoten nicht denselben Wurzelknoten haben, gibt es 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. Welcher zurückgegeben wird, ist implementierungsspezifisch. - Wenn
aein Vorfahre vonbist (einschließlich, wennbein Attribut vonaist), gibt esNode.DOCUMENT_POSITION_CONTAINS | Node.DOCUMENT_POSITION_PRECEDING(10) zurück. - Wenn
aein Nachfahre vonbist (einschließlich, wennaein Attribut vonbist), gibt esNode.DOCUMENT_POSITION_CONTAINED_BY | Node.DOCUMENT_POSITION_FOLLOWING(20) zurück. - Wenn
abin Baumreihenfolge vorausgeht, gibt esNode.DOCUMENT_POSITION_PRECEDING(2) zurück. - Wenn
abin Baumreihenfolge folgt, gibt esNode.DOCUMENT_POSITION_FOLLOWING(4) zurück.
Bitmaskenwerte werden verwendet, daher können Sie eine bitweise UND-Operation verwenden, um spezifische Beziehungen zu überprüfen. Zum Beispiel, um zu überprüfen, ob a b vorausgeht, können Sie:
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 eingeführt haben. Es sind viele, aber sie sind alle in verschiedenen Szenarien nützlich.
- Alle Knoten im DOM implementieren das
NodeInterface. - 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
nodeTypeEigenschaft gibt den Typ des Knotens an. DienodeName,nodeValueundtextContentEigenschaften liefern die von dem Knoten gehaltenen Daten. - Der
DocumentKnoten und seine zwei wichtigen Kinder:doctypeunddocumentElement. - Der
DocumentTypeKnoten und seine drei Eigenschaften:name,publicIdundsystemId. - Der
ElementKnoten und seine Eigenschaften:tagName,attributes. - Der
AttrKnoten und seine Eigenschaften:nameundvalue. - Das
CharacterDataInterface und seine Eigenschaft:data. - Die vier
CharacterDataUnterklassen:Text,CDATASection,CommentundProcessingInstruction.ProcessingInstructionhat auch dietargetEigenschaft. - Die verschiedenen Möglichkeiten, mit Attributen zu arbeiten, einschließlich der
id,classNameundclassListEigenschaften. - Die drei Methoden zum Vergleichen von Knoten:
isEqualNode(),isSameNode()undcompareDocumentPosition().