このページはコミュニティーの尽力で英語から翻訳されました。MDN Web Docs コミュニティーについてもっと知り、仲間になるにはこちらから。

View in English Always switch to English

DOM の構造

DOM は XML または HTML 文書をツリー構造として表します。このページでは、DOM ツリーの基本構造と、その操作に使用される様々なプロパティおよびメソッドについて紹介します。

まず、ツリーにまつわるいくつかの概念を紹介する必要があります。ツリーとは、ノードで構成されるデータ構造です。それぞれのノードはデータを保持します。ノードは階層的に組織化されており、それぞれのノードは単一の親ノード(親を持たないルートノードを除く)と、0 個以上の子ノードの順序付きリストを持ちます。これで以下の定義が可能です。

  • 親を持たないノードは、ツリーのルートと呼ばれます。
  • 子ノードを持たないノードは葉ノードと呼ばれます。
  • 同じ親を持つノードは兄弟ノードと呼ばれます。兄弟ノードは親の同一の子ノードリストに属するため、明確な順序が定義されています。
  • ノード A から親リンクを繰り返し追跡することでノード B へ到達可能な場合、A は B の子孫であり、B は A の祖先であるといえます。
  • ツリー内ノードは、まずノード自体を列挙し、次にそれぞれの子ノードを順序通りに再帰的に列挙する(先順序、深さ優先探索)というツリー順序でリスト化されます。

そして、以下のようなツリーに関するいくつかの重要なプロパティがあります。

  • 各ノードは単一のルートノードに関連付けられています。
  • ノード A がノード B の親である場合、ノード B はノード A の子であるといえます。
  • 循環は許容されません。いかなるノードも、自分自身の祖先または子孫であってはなりません。

Node インターフェイスとそのサブクラス

DOM 内のすべてのノードは、Node インターフェイスを実装するオブジェクトによって表されます。Node インターフェイスは、これまでに定義された概念の多くを体現しています。

  • parentNode プロパティあ h 親ノードを返します。ノードに親がない場合は null を返します。
  • childNodes プロパティは子ノード群の NodeList を返します。firstChild プロパティと lastChild プロパティはそれぞれこのリストの先頭と末尾の要素を返します。子ノードがない場合は null を返します。
  • getRootNode() メソッドは、親のリンクをたどって、このノードを含むツリーのルートを返します。
  • hasChildNodes() メソッドは、子ノードがある場合、つまり葉ノードではない場合に true を返します。
  • previousSibling プロパティと nextSibling プロパティは、それぞれ直前と直後の兄弟ノードを返します。そのような兄弟がない場合は null を返します。
  • contains() メソッドは、指定されたノードがこのノードの子孫である場合に true を返します。
  • compareDocumentPosition() メソッドは、 2 つのノードをツリー順で比較します。このメソッドはノードの比較の節で詳しく解説します。

純粋な Node オブジェクトを直接扱うことはほとんどありません。代わりに、DOM 内のすべてのオブジェクトは Node を継承したインターフェイスのいずれかを実装しており、これらは文書内の追加的な意味論を表します。ノードの型は、そのノードが保持するデータや有効な子ノードの型を制限します。次の HTML 文書が DOM でどのように表現されるかを考えてみましょう:

html
<!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>

これは次のような DOM ツリーを生成します。

前掲の HTML 文書の DOM ツリー

この DOM ツリーのルートは Document ノードであり、文書全体を表します。このノードは変数 document としてグローバルに公開されています。このノードには 2 つの重要な子ノードがあります。

  • DocumentType ノードはオプションで、doctype 宣言を表します。この場合は 1 つ存在します。このノードは Document ノードの doctype プロパティからもアクセスできます。
  • Element ノードはオプションで、ルート要素を表します。HTML 文書では(この場合のように)、これは通常は HTMLHtmlElement です。SVG 文書では、これは通常は SVGSVGElement です。このノードは Document ノードの documentElement プロパティからもアクセスでき案す。

DocumentType ノードは常に葉ノードです。Element ノードは、文書コンテンツの大部分が表される場所です。その下のそれぞれの要素、たとえば <head>, <body>, <p> も、Element ノードで表現されます。実際には、それぞれが HTML 仕様で定義された、そのタグ名に特化した Element のサブクラスです。たとえば HTMLHeadElementHTMLBodyElement などです。これらのサブクラスには、その要素の意味を表す追加のプロパティやメソッドがありますが、ここでは DOM の共通の動作に焦点を当てます。Element ノードは、他の Element ノードを子として持つことができ、入れ子になった要素を表します。例えば、<head> 要素には 3 つの子要素があります。2 つの <meta> 要素と 1 つの <title> 要素です。さらに、要素はテキストコンテンツを表す Text ノードや CDATASection ノードを子要素として持つこともできます。例えば、<p> 要素には単一の子要素として Text ノードがあり、そこには文字列 "This is a paragraph." が設定されています。Text ノードと CDATASection ノードは常に葉ノードです。

子を持つことができるすべてのノード (Document, DocumentFragment, Element) は、2 種類の子を持つことができます。Comment ノードと ProcessingInstruction ノードです。これらのノードは常に葉ノードです。

それぞれの要素は、子ノードに加えて、Attr ノードで表される属性を持つことができます。AttrNode インターフェイスを継承していますが、主要なツリー構造の一部にはなりません。子ノードを持つことがなく、親ノードが常に null だからです。その代わり、これらは名前付きノードマップに格納され、Element ノードの attributes プロパティからアクセスできます。

Node インターフェイスでは、ノードの型を示す nodeType プロパティを定義しています。要約すると、以下のノード型を導入しています。

ノード型 nodeType 有効な子ノード(CommentProcessingInstruction 以外で)
Document Node.DOCUMENT_NODE (9) DocumentType, Element
DocumentType Node.DOCUMENT_TYPE_NODE (10) なし
Element Node.ELEMENT_NODE (1) Element, Text, CDATASection
Text Node.TEXT_NODE (3) なし
CDATASection Node.CDATA_SECTION_NODE (4) なし
Comment Node.COMMENT_NODE (8) なし
ProcessingInstruction Node.PROCESSING_INSTRUCTION_NODE (7) なし
Attr Node.ATTRIBUTE_NODE (2) なし

メモ: いくつかのノード型を飛ばしたのに気が付いたかもしれません。Node.ENTITY_REFERENCE_NODE (5), Node.ENTITY_NODE (6), Node.NOTATION_NODE (12) の値は既に使われなくなっており、Node.DOCUMENT_FRAGMENT_NODE (11) の値は DOM ツリーの構築と更新で導入します。

各ノードのデータ

それぞれのノード型は、保持するデータを独自の方法で表します。Node インターフェイス自体には、データに関連する 3 つのプロパティが定義されており、以下の表に要約します。

ノード型 nodeName nodeValue textContent
Document "#document" null null
DocumentType その name"html" など) null null
Element その tagName ("HTML", "BODY"` など) null すべての子孫のテキストノードをツリー順に結合したもの
Text "#text" その data その data
CDATASection "#cdata-section" その data その data
Comment "#comment" その data その data
ProcessingInstruction その target その data その data
Attr その name その value その value

Document

文書 (Document) ノード自体は何もデータを持たないため、その nodeValuetextContent は常に null です。その nodeName は常に "#document" です。

Document は、環境(例えば、文書を提供した HTTP レスポンス)から取得される、文書に関するいくつかのメタデータを定義します。

  • URL プロパティと documentURI プロパティは、この文書の URL を返します。
  • characterSet プロパティは、この文書で使用される文字エンコーディング、例えば "UTF-8" を返します。
  • compatMode プロパティは、この文書のレンダリングモードを返します。"CSS1Compat"(標準モード)または"BackCompat"(後方互換モード)です。
  • contentType プロパティはこの文書のメディア型を返します。HTML 文書の場合は "text/html" などです。

DocumentType

文書内の文書型 (DocumentType) は、このようになっています。

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

指定することができる部分が 3 つあり、これは DocumentType ノードの 3 つのプロパティに対応します。name, publicId, systemId です。HTML 文書では、doctype は常に <!doctype html> であるため、name"html" であり、publicIdsystemId はどちらも空文字列です。

Element

文書内の要素 (Element) は、このようになっています。

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

コンテンツのほかに指定可能な部分は、タグ名と属性の 2 つがあります。タグ名は Element ノードの tagName プロパティに対応し、この例では "P" です(HTML 要素では常に大文字であることに注意してください)。属性は、Element ノードの attributes プロパティに格納される Attr ノードに対応します。属性については要素とその属性の節で詳しく説明します。

Element ノード自体はデータを保持しないため、その nodeValue は常に null です。その textContent は、ツリー順に並べたすべての子孫テキストノードを連結したもので、この場合 "This is a paragraph." となります。次の要素の場合、

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

textContent"Hello, world!" となります。テキストノードの "Hello, " と、<span> 要素内にあるテキストノードの "world"、それにテキストノード "!" を連結したものです。

CharacterData

Text, CDATASection, Comment, ProcessingInstruction はいずれも CharacterData インターフェイスから継承されており、これはさらに Node のサブクラスです。CharacterData インターフェイスは唯一のプロパティとして data を定義しています。これはノードのテキストコンテンツを保持します。data プロパティもこれらのノードの nodeValue プロパティと textContent プロパティを実装するために使用されます。

TextCDATASection については、data プロパティがノードのテキストコンテンツを保持します。以下の文書をご覧ください(なお、これは SVG 文書を使用しています。HTML は CDATA セクションを許可していないからです)。

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

<text> 要素の中のテキストノードは "Some text"data として保持しており、<style> 要素の中の CDATA セクションは "h1 { color: red; }"data として保持しています。

Comment では、data プロパティはコメントのコンテンツのうち <!-- の後から始まり --> の前で終わる部分を保持しています。例えば、次の文書の場合、

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

このコメントノードは " This is a comment "data として保持しています。

ProcessingInstruction において、data プロパティは処理命令のコンテンツを保持します。このコンテンツはターゲットの直後から始まり、?> の直前で終わります。例えば、次の文書では、

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

処理命令ノードは 'type="text/xsl" href="style.xsl"'data として、"xml-stylesheet"target として保持しています。

さらに、CharacterData インターフェイスは、data 文字列の長さを返す length プロパティと、data の部分文字列を返す substringData() メソッドを定義します。

Attr

次の要素について、

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

<p> 要素には 2 つの属性があり、2 つの Attr ノードで表されます。それぞれの属性は名前と値で構成され、それぞれ name プロパティと value プロパティに対応します。最初の属性は "class"name とし、"note"value として保持します。一方、2 つ目となる属性は "id"name とし、"intro"value として保持します。

要素とその属性

先述の通り、Element ノードの属性は Attr ノードで表され、これらは別個の名前付きノードマップに格納されます。このマップは Element ノードの attributes プロパティ経由でアクセス可能です。この NamedNodeMap インターフェイスは、以下の 3 つの重要なプロパティを定義しています。

  • length。これは属性の数を返します。
  • item() メソッド。これは指定された位置の Attr を返します。
  • getNamedItem() メソッド。これは指定された名前の Attr を返します。

Elementインターフェイスは、名前付きノードマップにアクセスすることなく、属性と直接操作するためのいくつかのメソッドも定義しています。

属性の所有要素には、Attr ノードの ownerElement プロパティを介してアクセスすることも可能です。

特別な属性として、 idclass は、Element インターフェイス上にそれぞれ、idclassName の固有のプロパティを持ちます。これらは対応する属性の値を反映します。さらに、classList プロパティは、class 属性内のクラス一覧を表す DOMTokenList を返します。

要素ツリーでの作業

Element ノードは文書構造の骨格を形成するため、他のノード(TextComment など)をスキップして、要素ノードのみを特定して走査することができます。

  • すべてのノードにおいて、parentElement プロパティは、親ノードが Element である場合にその親ノードを返し、親が Element でない場合(例えば親が Document である場合)には null を返します。これは、親ノードの型に関係なく親ノードを返す parentNode とは対照的です。
  • Document, DocumentFragment, Element については、children プロパティは子ノードのうち Element のみを HTMLCollection で返します。これは、childNodes がすべての子ノードを返すのとは対照的です。firstElementChild プロパティと lastElementChild プロパティは、それぞれこの集合の最初と最後の要素を返し、子要素がない場合は null を返します。childElementCount プロパティは、子要素の数を返します。
  • ElementCharacterData については、previousElementSibling プロパティと nextElementSibling プロパティは Element である前または次の兄弟ノードを返し、そのような兄弟ノードがない場合は null を返します。これは previousSiblingnextSibling が、あらゆる型の兄弟ノードを返す可能性があるのとは対照的です。

ノードの比較

ノードを比較するためには 3 つの重要なメソッドがあります。isEqualNode(), isSameNode(), compareDocumentPosition() です。

isSameNode() メソッドは古いものです。これは厳密等価演算子 (===) のように動作し、2 つのノードが同じオブジェクトである場合のみ true を返します。

isEqualNode() メソッドは、2 つのノードを構造的に比較します。2 つのノードは、同じ型を持ち、同じデータを持ち、かつ各インデックスにおける子ノードも等しい場合に等しいと見なされます。 各ノードのデータの節では、各ノード型に関連するデータを既に定義しています。

  • Document については、データがなく、比較する必要があるのは子ノードだけです。
  • DocumentType については、name, publicId, systemId の各プロパティを比較する必要があります。
  • Element については、tagName(より正確には namespaceURIprefixlocalName。これらは XML 名前空間ガイドで紹介します)と属性を比較する必要があります。
  • Attr に対しては、name(より正確には namespaceURIprefixlocalName。これらはXML 名前空間ガイドで紹介します)と value プロパティを比較する必要があります。
  • すべての CharacterData ノード (Text, CDATASection, Comment, ProcessingInstruction) では、data プロパティを比較する必要があります。ProcessingInstruction については、target プロパティも比較する必要があります。

a.compareDocumentPosition(b) メソッドは、ツリー順で 2 つのノードを比較します。これらは相対的な位置関係を示すビットマスクを返します。取りうる場合は次の通りです。

  • ab が同じノードであれば 0 を返します。
  • 2 つのノードが両方とも同じノードの属性であれば、属性リスト内で ab よりも先にある場合は Node.DOCUMENT_POSITION_PRECEDING | Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC (34)、または a の後に b がある場合は Node.DOCUMENT_POSITION_FOLLOWING | Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC (36) を返します。どちらかのノードが属性である場合は、それ以降の比較にはオーナー要素が使用されます。
  • 2 つのノードのルートノードが同じでない場合は、Node.DOCUMENT_POSITION_DISCONNECTED | Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC | Node.DOCUMENT_POSITION_PRECEDING (35) または Node.DOCUMENT_POSITION_DISCONNECTED | Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC | Node.DOCUMENT_POSITION_FOLLOWING (37) を返します。どちらが返されるかは実装依存です。
  • ab の祖先である場合(ba の属性である場合も含む)、Node.DOCUMENT_POSITION_CONTAINS | Node.DOCUMENT_POSITION_PRECEDING (10) を返します。
  • ab の子孫である場合(ab の属性である場合も含む)、Node.DOCUMENT_POSITION_CONTAINED_BY | Node.DOCUMENT_POSITION_FOLLOWING (20) を返します。
  • a がツリー順で b よりも前にある場合、Node.DOCUMENT_POSITION_PRECEDING (2) を返します。
  • a がツリー順で b よりも後にある場合、 Node.DOCUMENT_POSITION_FOLLOWING (4) を返します。

ビットマスク値が使用されるため、特定の関係を調べるにはビット単位の AND 演算を使用できます。例えば、ab より前にあるかどうかを確認するには、次のようにするのが最適です。

js
if (a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_PRECEDING) {
  // a が b より前にある
}

これは、ab が同じ要素の属性であり、ab の祖先であり、かつ a がツリー順序において b より前に位置する場合の状況を説明しています。

まとめ

これまでに導入した機能のすべてをご紹介します。数は多いですが、どれも様々な場面で有益です。