DOMとは?基礎から操作・イベント・パフォーマンス・XSS対策まで徹底解説

DOM とは — 概要

DOM(Document Object Model、ドキュメントオブジェクトモデル)は、HTML や XML の文書をプログラムから扱うための抽象的なモデルと、そのモデルへアクセス・操作するためのAPI群を指します。ブラウザでは、HTML テキストがパースされると DOM ツリーが生成され、JavaScript などからノードの追加・削除・変更、イベントの取り扱いができるようになります。DOM は単なるデータ構造ではなく、ブラウザの描画やイベント処理と密接に結びついた「生きた」APIです。

歴史と標準化

  • 1990年代後半に W3C によって DOM Level 1/2/3 の仕様が策定されました。これらは当時のブラウザ間互換性を目的とした仕様群です。

  • 近年は WHATWG(Web Hypertext Application Technology Working Group)による「HTML Living Standard」や「DOM Standard(WHATWG DOM)」が事実上のリファレンスになっており、ブラウザ実装はこれらのリビングスタンダードに従うことが多いです。

  • 影響の大きい関連仕様として、Shadow DOM、Custom Elements(Web Components)、HTML Parsing、Event 規格などがあります。

DOMツリーとノードの種類

DOM は階層的なツリー構造(ノードツリー)で表現されます。各要素は Node インタフェースを継承したオブジェクトで、代表的なノードタイプには次のものがあります。

  • ELEMENT_NODE (1) — 要素ノード(<div> 等)
  • ATTRIBUTE_NODE (2) — 属性ノード(歴史的存在。現在は属性は要素のプロパティや attributes コレクション経由で扱う)
  • TEXT_NODE (3) — テキストノード
  • COMMENT_NODE (8) — コメントノード
  • DOCUMENT_NODE (9) — ドキュメントルート
  • DOCUMENT_FRAGMENT_NODE (11) — DocumentFragment(断片。バッチ操作で便利)

その他にも仕様上のノードタイプがありますが、実務では ELEMENT_NODE、TEXT_NODE、COMMENT_NODE、DOCUMENT_FRAGMENT、DOCUMENT_NODE を意識することが多いです。

属性(attributes)とプロパティ(properties)の違い

DOM では HTML の「属性」と DOM 要素オブジェクトの「プロパティ」は別物です。例えば <input value="foo"> の場合:

  • element.getAttribute('value') は初期属性値(マークアップ中の値)を返す。
  • element.value(プロパティ)は現在の入力値(ユーザー操作で変化する)を返す。

他にも class 属性は element.className、data-* 属性は element.dataset を使うなど、属性とプロパティのマッピングルールを理解することが重要です。

DOM の操作と API

代表的な操作と API は次の通りです。

  • ノードの取得: document.getElementById, getElementsByClassName, getElementsByTagName, querySelector, querySelectorAll
  • ノードの生成・挿入: document.createElement, createTextNode, appendChild, insertBefore, replaceChild, cloneNode
  • ノードの削除: removeChild, element.remove
  • 属性の操作: setAttribute, getAttribute, removeAttribute, element.attributes(NamedNodeMap)
  • HTML の操作: innerHTML(パース/シリアライズ)、outerHTML
  • テンプレート利用: <template> 要素と DocumentFragment
  • シリアライズ: XMLSerializer(主に XML)、outerHTML(HTML)

ノード集合 — Live と Static

注意点として、DOM が返すコレクションには「ライブ(live)」なものと「静的(static)」なものがあり、挙動が異なります。

  • ライブ: getElementsByTagName, getElementsByClassName, children, childNodes など。DOM を変更するとコレクションも自動更新される。
  • 静的: querySelectorAll は静的な NodeList を返す(取得時点のスナップショット)。

ライブコレクションは便利ですが、ループ中に要素を追加・削除すると予期せぬ挙動を招くため注意が必要です。

イベントとイベント伝播

DOM イベントは UI 操作やネットワークなどの事象を扱います。重要な概念:

  • addEventListener(type, listener, options) — options は capture, once, passive などを指定できる。
  • 伝播フェーズ: キャプチャ(capture)→ターゲット→バブリング(bubble)。イベントは既定でバブリングすることが多い。
  • stopPropagation() / stopImmediatePropagation() / preventDefault() — 伝播停止やデフォルト動作の抑止。
  • イベントデリゲーション — 親要素にイベントリスナを置き、子孫のイベントを処理する手法。パフォーマンスとメモリ効率の向上に有効。

変更の監視 — MutationObserver

以前は Mutation Events が使われましたが、現在は MutationObserver が推奨です。DOM の追加・削除や属性変更を非同期で監視でき、頻繁な変更に対して高性能です。

const observer = new MutationObserver((mutations) => {
  for (const m of mutations) {
    console.log(m.type, m.target);
  }
});
observer.observe(document.body, { childList: true, subtree: true, attributes: true });

パフォーマンス上の注意点

  • レイアウト(リフロー)/ リペイントを誘発する操作は高コスト。頻繁な読み取り(offsetWidth など)と書き込み(style 操作)を交互に行うと強制レイアウトが発生する。
  • バッチ更新: DocumentFragment を使って DOM 変更をまとめて行う、innerHTML で一括差し替えする、もしくは requestAnimationFrame でタイミングを合わせる。
  • クラス操作は classList を使う(className を直接編集するより安全かつ効率的)。
  • 大規模な UI では仮想 DOM(Virtual DOM)や差分更新アルゴリズムを用いるフレームワーク(React、Vue 等)を検討すると良い。

モダン機能:Shadow DOM と Web Components

Shadow DOM は要素の内部的な DOM をカプセル化し、スタイルや DOM の衝突を防ぐ仕組みです。Custom Elements と組み合わせることで、再利用可能で自己完結したコンポーネントを作成できます。

  • shadowRoot は open / closed モードがあり、open の場合は外部から shadowRoot にアクセス可能。
  • slot を使ってシャドウ内に外部コンテンツを差し込むことができる。

セキュリティと XSS(クロスサイトスクリプティング)

innerHTML 等で外部入力をそのまま挿入すると XSS の原因になります。ユーザー生成コンテンツを表示する際はサニタイズライブラリやテキストノード(textContent)を使い、危険なスクリプト挿入を防いでください。また、CSP(Content Security Policy)を導入すると追加の防御層になります。

DOM と HTML パーサの関係

ブラウザは HTML をパースして DOM を生成します。HTML のパースアルゴリズムは WHATWG の HTML Living Standard に定義されており、ブラウザ間で互換性を取るための多くの細かいルールがあります。スクリプト実行や document.write による動的挿入はパーサの挙動に影響を与えるため注意が必要です。

ユースケース別の実践的アドバイス

  • 動的なリスト更新:DocumentFragment でまとめて操作する。
  • 多くの要素を検索する:querySelectorAll を使って静的 NodeList を得てから操作する(ライブ更新の副作用を避ける)。
  • イベント処理:イベントデリゲーションを使い、個々の要素に大量のリスナを登録しない。
  • フォーム値の取得:属性ではなくプロパティ(.value)を使う。
  • テンプレート:<template> 要素で HTML 断片を安全に保持し、必要時にクローンして使う。

開発ツールとデバッグ

ブラウザの DevTools(Elements/Inspector、Event Listeners、Performance、Memory)を使えば DOM の構造確認、イベントリスナの追跡、レイアウトコストの計測ができます。MutationObserver を一時的に使って DOM 変化をログするのも有効です。

まとめ

DOM はウェブアプリケーション実装の中心的概念であり、ツリー構造、ノードタイプ、属性とプロパティの違い、イベント伝播、パフォーマンス特性、セキュリティ上の注意点など多くの要素を理解する必要があります。モダンな仕様(WHATWG DOM、Shadow DOM、Custom Elements)を踏まえた実装は、より堅牢で再利用性の高いコードに繋がります。実運用ではブラウザの挙動差やライブコレクション、強制レイアウトを意識して設計してください。

参考文献