DOM(ドキュメント・オブジェクト・モデル)とは?仕組みやツリー構造・JavaScriptでの操作例を分かりやすく解説

DOM(ドキュメント・オブジェクト・モデル)とは?仕組み・ツリー構造・操作例を分かりやすく解説 アイキャッチ画像

Web制作やJavaScriptを学んでいると、必ず早い段階で登場するのが「DOM(ドム:Document Object Model/ドキュメント・オブジェクト・モデル)」という言葉です。

解説書などで「HTML文書をオブジェクトとして扱うためのインターフェース」と説明されても、「結局HTMLそのものと何が違うの?」「DOMツリーやノードってどういう意味?」「JavaScriptでDOMを操作するとは具体的に何をしているの?」とイメージしづらく感じる方も多いのではないでしょうか。

本記事では、DOMの仕組みやHTMLとの違いを身近なたとえ話と図解で噛み砕いて解説したうえで、ツリー構造(ノード)の基本、そのまま動かして試せるJavaScriptのDOM操作コード例、そして実務で欠かせないセキュリティ対策や「仮想DOM」との関係まで分かりやすく紐解きます。

DOM(Document Object Model)とは?一言でいうと「HTMLをプログラムから操るための窓口」

DOM(Document Object Model:ドキュメント・オブジェクト・モデル)の意味は、3つの英単語に分解するとぐっと分かりやすくなります。

  • Document(ドキュメント):Webページの文書(HTMLやXML)のこと
  • Object(オブジェクト):プログラムから個別に触れる「部品(モノ)」のこと
  • Model(モデル):部品同士の親子関係を整理した「設計モデル(立体模型)」のこと

つまりDOMとは、一言でいうと「WebブラウザがHTMLを読み込んでメモリ上に組み立てた立体模型であり、JavaScriptなどのプログラムから画面を自由に書き換えるための窓口(仕組み)」のことです。

HTMLファイル(文字だけの設計書)をブラウザがDOM(階層ツリーの立体模型)に変換し、JavaScriptから画面を動的に書き換える関係を示した図解
ブラウザがHTMLテキストを「DOM」という部品の集合体に変換してくれるおかげで、JavaScriptから画面を自在に動かせる

「HTMLファイル」と「DOM」は何が違うのか?

「HTMLとDOMは同じものではないの?」と思われがちですが、両者の役割は明確に異なります。家に例えるなら、「HTMLファイル = 紙に書かれた設計書」であり、「DOM = 設計書をもとにブラウザの中で組み立てられた立体模型」です。

サーバーから送られてくる index.html ファイルの中身は、実はただの「長い文字列(テキストデータ)」にすぎません。単なる文字の羅列のままでは、JavaScriptから「3番目のリスト項目だけ背景を黄色にする」「ボタンが押されたら新しい見出しを差し込む」といった細かな操作を行うのはとても大変です。

そこでChromeやSafariなどのWebブラウザは、HTMLファイルを読み込んだ瞬間にタグの構造を解析し、見出し・ボタン・画像といった一つひとつの要素をプログラムから直接触れる「オブジェクト(部品)」に変換します。JavaScriptはこのDOM(模型)をリモコンのように操作することで、ページ全体を再読み込み(リロード)しなくても、目の前の画面をその場でサクサク変化させられるのです。

ブラウザで実感!「ページのソースを表示」と「検証(開発者ツール)」の違い

HTMLとDOMの違いを自分の目で確かめられる一番わかりやすい方法が、Webブラウザの右クリックメニューにある2つの機能を比べることです。

  • 「ページのソースを表示」で見えるもの = 元のHTMLテキスト:サーバーから最初に届いた生のHTMLファイルがそのまま表示されます。あとからJavaScriptで画面の文字を書き換えたり要素を追加したりしても、こちらのソース表示は最初のまま変化しません。
  • 「検証(開発者ツール/Elementsタブ)」で見えるもの = 現在のDOM:ブラウザが現在メモリ上に保持しているリアルタイムのDOM構造が表示されます。JavaScriptでメニューを開いたり要素を追加したりすると、検証画面の中のタグもその瞬間にキラッと光って書き換わります。また、元のHTMLに閉じタグの書き忘れなどの軽微なミスがあった場合、ブラウザが自動補正してDOMを組み立てるため、元のHTMLとDOMの形が変わることもあります。

DOMはJavaScriptの言語仕様(ECMAScript)ではなく「Web API」

もうひとつ整理しておきたい大切な知識が、DOMはJavaScriptそのものの文法ルール(ECMAScript)ではなく、Webブラウザ側が用意している標準機能(Web API)だという点です(言語仕様とブラウザ機能の違いについてはECMAScriptとは?JavaScriptとの違いや歴史でも詳しく解説しています)。
DOMの仕様は現在、国際的な標準化コミュニティである WHATWG(DOM Living Standard) によって策定されているため、特定のブラウザや言語に縛られません。実務で最もよくDOMを操るのはブラウザ上のJavaScriptですが、PythonのWebスクレイピングや自動テストツール(Playwrightなど)でも、同じ「DOM」の考え方を使ってWebページ上の要素を探したり操作したりすることができます。

DOMを使うと何ができる?身近なWebサイトの具体例

「DOM操作」と聞くと少し専門的に聞こえますが、私たちが毎日スマートフォンやPCで触れているWebサイトの便利な動きは、そのほとんどがJavaScriptによるDOM操作で作られています。身近な具体例をいくつか見てみましょう。

  • ハンバーガーメニューやアコーディオンの開閉:スマホ画面の右上にある三本線ボタンをタップした瞬間、隠れていたメニュー要素のCSSクラス(is-open など)をDOM操作で付け替えて、スッと画面内に表示させます(実際の作り方はハンバーガーメニューの実装手順と仕組みをご参照ください)。
  • 入力フォームのリアルタイムチェックと自動入力:会員登録画面で「パスワードは8文字以上で入力してください」とその場で赤字の注意文を出したり、残り文字数カウンターを「あと45文字」とリアルタイムに減らしたり、郵便番号から住所欄の値を自動セットしたりする動きもDOM操作です。
  • 「いいね!」ボタンやカート件数の即時カウントアップ:SNSでハートアイコンを押した瞬間にページ遷移なしでアイコンを赤く変えて数字を +1 したり、ネットショップで「カートに入れる」を押したときに右上のバッジ件数を更新したりします。
  • タブ切り替え・モーダル表示・ダークモード切り替え:「新着/人気/おすすめ」のタブを押して表示エリアの中身だけを切り替えたり、スイッチひとつで <html> 要素の属性を書き換えて画面全体をダークモード配色に変えたりするのも代表的な活用例です。

DOMの「ツリー構造(DOMツリー)」と「ノード(Node)」の仕組み

ブラウザがHTMLをDOMに変換するとき、タグの「入れ子(中に別のタグが入っている関係)」をもとに、家系図や会社の組織図のように上から下へ枝分かれしていく「ツリー構造(木構造:DOMツリー)」として整理します。

そして、このDOMツリーを構成している一つひとつの部品(枝の結び目や葉っぱ)のことを「ノード(Node)」と呼びます。

documentを頂点としてhtml・body・h1・buttonや文字データが親子でつながるDOMツリーの階層構造と主なノードの種類の図解
HTMLの入れ子構造が「親・子・兄弟」のツリー状になるため、プログラムから目的の部品を正確に見つけ出せる

簡単なHTMLが「DOMツリー」になる具体例

たとえば、次のようなごく短いHTMLファイルがあるとします。

<!DOCTYPE html>
<html lang="ja">
  <body>
    <h1 id="main-title">ようこそ</h1>
    <button class="submit-btn">送信する</button>
  </body>
</html>

ブラウザがこのHTMLを読み込むと、次のようにつながったDOMツリーが作られます。

  • 一番上の根っこ(ルート)には、ページ全体を代表する document(ドキュメントノード) が置かれます。JavaScriptで画面の要素を探すときにいつも document.〜 と書き始めるのは、「ツリーの頂点であるdocumentから探し始めますよ」という意味だからです。
  • <body> の中には <h1> と <button> が入っています。このとき、<body> を「親要素(Parent)」、<h1> と <button> を「子要素(Children)」、同じ親を持つ <h1> と <button> 同士を「兄弟要素(Siblings)」と呼びます。
  • さらに <h1> という要素ノードの内側には "ようこそ" というテキストノードがぶら下がり、要素には id="main-title" や class="submit-btn" といった属性(Attribute)が紐づいています。
部品(ノード等)の種類 具体例 DOMツリー内での役割
ドキュメントノード
(Document)
document Webページ全体を表すDOMツリーの頂点。要素の検索や新規作成の起点になる。
要素ノード
(Element)
<h1> <div> <ul> など HTMLの各タグに対応する部品。JavaScriptで取得・変更・追加する際の中心的な対象。
テキストノード
(Text)
"ようこそ" など タグの内側に含まれる実際の文字列データ(HTML内の改行や空白もテキストノードになる)。
属性
(Attribute)
id class href src 要素ノードにセットされた目印や設定値。要素を特定したり、リンク先や画像を切り替えたりするのに使う。
コメントノード
(Comment)
<!-- メモ --> HTML内のコメント。ブラウザ画面には表示されないが、DOMツリー上にはデータとして残る。

【実践コード例】JavaScriptでDOMを操作する基本パターン

DOM関連のメソッド(命令)はたくさんあるように見えますが、実務で書く処理は突き詰めると「要素を取得する」「中身や見た目を変える」「要素を追加・削除する」「イベント(操作)に反応させる」の4パターンだけです。それぞれの具体的な書き方をコード例で見ていきましょう。

JavaScriptでDOMを操る4つの基本操作(要素を取得する・中身や見た目を変更する・要素を追加や削除する・イベントで動かす)の図解
どんなに複雑なWeb画面の動きも、この「4つの基本操作」の組み合わせで作られている

要素を取得する(querySelector / getElementById)

画面を書き換えるには、まず「どの要素を操作するか」をDOMツリーから見つけて変数に入れます。現在のJavaScript開発では、CSSとまったく同じ記法(#id名、.クラス名、ul > li など)で柔軟に指定できる querySelector / querySelectorAll と、ID指定専用の getElementById を使うのが主流です。

// ID名を指定して1つの要素を取得する(#は付けない)
const titleEl = document.getElementById('main-title');

// CSSセレクタ記法で最初に一致した1つの要素を取得する
const submitBtn = document.querySelector('.submit-btn');

// 条件に一致する「すべての要素」をまとめて取得し、順番に処理する
const menuItems = document.querySelectorAll('.nav-list li');
menuItems.forEach((item) => {
  console.log(item.textContent);
});

文字・属性・見た目(CSSクラス)を書き換える

取得した要素に対して、表示テキストの変更や画像の差し替え、デザインの切り替えを行います。なお、色や表示・非表示などの見た目を変えるときは、JavaScriptで直接 style.color = 'red' のように書くよりも、classList でCSSクラスを付け外しするほうがデザインとプログラムをきれいに分離できて管理しやすくなります。

const heading = document.querySelector('#main-title');
const heroImage = document.querySelector('#hero-img');
const card = document.querySelector('.notice-card');

// 文字を安全に書き換える(最もよく使う基本プロパティ)
heading.textContent = 'DOM操作でタイトルを更新しました!';

// 画像のURLや代替テキスト(属性)を書き換える
heroImage.src = 'https://example.com/photo-after.jpg';
heroImage.setAttribute('alt', '更新後のイメージ画像');

// CSSクラスを追加・削除・ON/OFF反転(トグル)する
card.classList.add('is-active');      // クラスを追加
card.classList.remove('is-hidden');   // クラスを削除
card.classList.toggle('dark-theme');  // 付いていれば外し、無ければ付ける

新しい要素を作って画面に追加・削除する

もともとHTMLには書かれていなかった新しいタグを document.createElement() で作成し、指定した親要素の中へ append()(末尾に追加)や prepend()(先頭に追加)で差し込むことができます。不要になった要素は remove() だけで画面から取り除けます。

const list = document.querySelector('#todo-list');

// 新しい <li> 要素ノードをメモリ上で作成する
const newItem = document.createElement('li');
newItem.textContent = '牛乳と卵を買う';
newItem.classList.add('todo-item');

// 親要素(<ul>)の末尾に差し込む(この瞬間に画面へ表示される!)
list.append(newItem);

// 要素を画面(DOMツリー)から削除したいとき
// newItem.remove();

【コピペで動く完成例】イベントと組み合わせた「簡易メモ追加アプリ」

最後に、ユーザーのクリックやキー入力を検知する addEventListener を使い、ここまでの4つの操作をすべて組み合わせたミニアプリの完成コードをご紹介します。以下のコードを memo.html などの名前で保存してブラウザで開くと、ページを再読み込みせずにメモの追加・削除・件数カウントが動く様子をそのまま体験できます。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>DOM操作の体験サンプル</title>
  <style>
    .done-item { display: flex; gap: 12px; margin-bottom: 8px; align-items: center; }
    .error-msg { color: #dc2626; font-size: 0.9em; min-height: 1.4em; }
  </style>
</head>
<body>
  <h1>やることメモ(現在 <span id="count">0</span> 件)</h1>

  <input id="memo-input" type="text" placeholder="メモを入力してください">
  <button id="add-btn" type="button">追加する</button>
  <p id="error-text" class="error-msg"></p>

  <ul id="memo-list"></ul>

  <script>
    // 必要なDOM要素を取得
    const inputEl = document.querySelector('#memo-input');
    const addBtn = document.querySelector('#add-btn');
    const errorEl = document.querySelector('#error-text');
    const listEl = document.querySelector('#memo-list');
    const countEl = document.querySelector('#count');

    // 件数表示を現在のリスト数に合わせて更新する関数
    const updateCount = () => {
      countEl.textContent = listEl.children.length;
    };

    // 「追加する」ボタンがクリックされたときのイベント処理
    addBtn.addEventListener('click', () => {
      const text = inputEl.value.trim();

      // 空欄チェック(エラー文のDOM書き換え)
      if (text === '') {
        errorEl.textContent = '文字を入力してから追加ボタンを押してください。';
        return;
      }
      errorEl.textContent = '';

      // 新しい <li> と削除用 <button> を作成
      const li = document.createElement('li');
      li.classList.add('done-item');

      const span = document.createElement('span');
      span.textContent = text;

      const deleteBtn = document.createElement('button');
      deleteBtn.type = 'button';
      deleteBtn.textContent = '削除';
      deleteBtn.addEventListener('click', () => {
        li.remove();   // この行をDOMから削除
        updateCount(); // 件数を再計算
      });

      // 部品を組み立てて画面のリストへ追加
      li.append(span, deleteBtn);
      listEl.append(li);

      // 入力欄を空に戻して件数を更新
      inputEl.value = '';
      inputEl.focus();
      updateCount();
    });
  </script>
</body>
</html>

実務で押さえておきたいDOM操作の注意点と「仮想DOM」

DOMの基本操作が分かったところで、実際のWeb制作やアプリケーション開発でつまずきやすい注意点と、ReactやVueなどのモダン開発フレームワークで採用されている「仮想DOM」の仕組みを押さえておきましょう。

直接DOM操作を行う際の3つの実務注意点(XSS対策・defer属性・再描画の抑制)とReact等の仮想DOMによる差分更新の仕組みを比較した図解
生のJavaScriptでは安全性と実行タイミングに注意し、複雑なWebアプリでは「仮想DOM」を持つフレームワークが活躍する

「要素が null でエラーになる」を防ぐ読み込みタイミング(defer属性)

DOM操作を学び始めた方が最もよく遭遇するエラーが、Cannot set properties of null (setting 'textContent') です。これは、HTMLの <head> タグ内で外部JavaScriptファイルを普通に読み込んだとき、ブラウザがまだ <body> 内のHTMLを読み終えておらず(=DOMツリーがまだ作られていない状態で)要素を探しに行ってしまうために起こります。

これを防ぐには、外部スクリプトを読み込む <script> タグに defer 属性を付けるのが現在の標準的なベストプラクティスです。

<!-- defer を付けると、HTMLの解析(DOM構築)が完了してから安全に実行される -->
<head>
  <script src="app.js" defer></script>
</head>

セキュリティ(XSS対策):文字を入れるときは「innerHTML」ではなく「textContent」

要素の中身を書き換えるプロパティには textContent のほかに、HTMLタグごと解釈して流し込める innerHTML もあります。
しかし、検索フォームやコメント欄など「ユーザーが入力した文字列や外部データ」をそのまま innerHTML に代入してはいけません。もし入力値の中に悪意あるプログラム(<img src="x" onerror="..."> など)が紛れ込んでいると、ブラウザ上で不正なスクリプトが実行されてしまうXSS(クロスサイトスクリプティング)の被害につながるからです。textContent を使えば記号が単なる文字として安全に表示されるため、文字の更新には常に textContent を選ぶ習慣をつけましょう。

連続更新のパフォーマンスと「DocumentFragment」

画面に表示されているDOMを書き換えるたびに、ブラウザの裏側では「要素の位置や大きさの再計算(リフロー)」や「画面の塗り直し(リペイント)」という比較的負荷の高い処理が走ります。そのため、たとえばAPIから取得した500件の商品データをループで1件ずつ直接画面の <ul> に append すると、500回も画面更新が走って動作が重くなる原因になります。
大量の要素を追加するときは、画面外の軽い仮コンテナである DocumentFragment に一度まとめてから、最後に1回だけ画面のDOMへ反映するとスムーズに描画できます。

const list = document.querySelector('#product-list');
// メモリ上の「仮の入れ物」を作成(ここに追加しても画面の再描画は起きない)
const fragment = document.createDocumentFragment();

for (let i = 1; i <= 100; i++) {
  const li = document.createElement('li');
  li.textContent = `アイテム ${i}`;
  fragment.append(li);
}

// 最後に1回だけ本物のDOMへ反映する!
list.append(fragment);

ReactやVueの「仮想DOM(Virtual DOM)」と「Shadow DOM」とは?

DOMの基礎を理解しておくと、現代のフロントエンド開発でよく耳にする2つの発展用語もすっきりと区別できるようになります。

  • 仮想DOM(Virtual DOM):ReactやVue.jsなどのUIライブラリが使っている高速化・省力化の仕組みです。大規模な管理画面などのSPA(シングルページアプリケーション)では、データが変化するたびに開発者が手動で querySelector を使ってDOMを書き換えているとコードが複雑になり、更新漏れなどのバグが起きやすくなります。そこでReactなどは、JavaScriptのメモリ上に軽量な仮想のDOMツリーを持ち、状態が変わったときに「変更前と変更後の差分(Diff)」だけを瞬時に計算して、本当に変わった箇所だけを本物のDOM(実DOM)へ自動反映してくれます(SPAとSSRにおける画面描画の使い分けについてはReact RouterでのSSRとSPAの仕組み解説もあわせてご覧ください)。
  • Shadow DOM(シャドウDOM):仮想DOMと名前が似ていますがまったく別の技術で、ブラウザ標準の「Web Components」を構成する仕様のひとつです。ページ全体のDOMツリーから隔離された「独立した小さなDOM空間」を作る仕組みで、外部のCSSの影響を受けない再利用パーツを作るときなどに利用されます(実はブラウザ標準の <video> タグの再生ボタンなども、裏側ではこのShadow DOMで作られています)。

まとめ:よく使うDOM操作メソッド・プロパティ早見表

最後に、JavaScriptでDOMを操作するときに頻繁に登場する定番のメソッド・プロパティを目的別に一覧表へ整理します。

やりたいこと 代表的な書き方 特徴・使い分けのポイント
要素を1つ取得 document.querySelector('.box')
document.getElementById('btn')
CSSセレクタで柔軟に探すなら querySelector、IDが確定しているなら getElementById。
要素を複数取得 document.querySelectorAll('li') 一致する全要素のリスト(NodeList)を取得。forEach で順番に処理できる。
文字を書き換える el.textContent = '新しい文字' HTMLタグを無害化して純粋な文字として反映するため、XSS対策の面でも安全。
属性・クラスの操作 el.setAttribute('href', url)
el.classList.toggle('is-open')
見た目の切り替えは classList(add / remove / toggle)で行うのが定石。
要素の作成・追加・削除 document.createElement('div')
parent.append(child) / el.remove()
新しい要素を作って親の末尾(先頭なら prepend)に挿入、不要なら remove()。
イベントを登録する el.addEventListener('click', fn) クリック(click)、入力(input)、送信(submit)などをきっかけに関数を実行。

本記事のまとめ

  • DOM(Document Object Model)とは、文字データであるHTMLをブラウザが解析して組み立てた「親子ツリーの立体模型」であり、プログラムから画面を操作するための窓口(Web API)である。
  • DOMツリーを構成する各部品をノード(要素ノード・テキストノードなど)と呼び、ブラウザの「検証(開発者ツール)」を開けばリアルタイムに書き換わる現在のDOMをいつでも確認できる。
  • JavaScriptでのDOM操作は「取得・変更・追加削除・イベント登録」の4パターンが基本。実務では defer 属性による読み込み順序の制御や、textContent を使ったXSS対策を意識することが大切。

DOMの仕組みが頭の中でイメージできるようになると、普段のJavaScriptコードが「画面の裏側で何をしているのか」が手に取るように分かり、Reactなどのフレームワークを学ぶ際にも確かな土台になります。ぜひ手元のブラウザの開発者ツールを開いたり、サンプルコードを動かしたりしながら試してみてください。


eguchi.netをもっと見る

購読すると最新の投稿がメールで送信されます。