こんにちは。フロントエンドの現場で日々、パフォーマンスチューニングや不可解なレンダリングバグと格闘している君なら、「DOM(Document Object Model)がどうやって作られているか」を一度は意識したことがあるはずだ。
「HTMLを書いて、ブラウザがそれを読めば勝手にツリーができるんでしょ?」
――まあ、大正解だ。だが、シニアの視点から言わせてもらうと、この「勝手にやってくれる裏側の仕組み」を理解しているか否かで、書くHTMLの質、そして何よりWebアプリのパフォーマンスが劇的に変わる。
今回は、ネットワークの向こうから流れてきたバイト列が、あの厄介で愛おしい「DOMツリー」に化けるまでの泥臭い裏側と、実務で絶対に知っておくべきエラーハンドリングの仕様について、徹底的に紐解いていこう。
—
1. バイト列からDOMへ:レンダリングエンジンの裏側の舞台裏
ブラウザ(ChromiumならBlink、WebKitなど)がHTMLファイルを読み込むとき、実は一気にツリーを作っているわけじゃない。いくつかの残酷なまでのステップを踏んでいる。
1. バイト列のデコード: ネットワークから受信した生データ(`010101…`)を、指定された文字コード(UTF-8など)で文字列に変換する。
2. トークン化 (Tokenization): 文字列をW3Cの仕様(HTML5 Spec)に基づき、「開始タグ」「終了タグ」「属性」「テキスト」といった最小単位のトークンに切り分けていく。
3. ツリー構築 (Tree Construction): トークンを元にオブジェクト(Node)を生成し、親子関係(DOM)を組み立てていく。
ここで重要なのが、「HTMLのパースとDOMツリーの構築は、ストリーミング(インクリメンタル)で行われる」という点だ。
ブラウザは、HTMLファイルがすべてダウンロードされるのを待ったりしない。ネットワークからチャンク(断片)が届くたびに、絶えずトークン化し、DOMツリーを構築し、画面を描画しようとする。この「途中経過」を無理やりJavaScriptから触らせるのが、世も末なインラインスクリプトの悪夢の始まりなわけだが……それはまた別の機会にしよう。
トークン化の狂気:状態機械(State Machine)
HTMLのパース仕様書を読んだことがあるかい? あれは地獄のような「状態機械(Finite State Machine)」の塊だ。
例えば、パーサが `<` を見つけると「タグオープン状態」になり、そこからアルファベットが来ると「タグ名状態」に遷移する。もし途中で `` が来るまでひたすら文字を飲み込み続ける。
人間が書く適当なHTMLを、ブラウザが何食わぬ顔で解釈できるのは、この厳密な状態機械のおかげなのだ。
—
2. 驚異の「エラー耐性」:不正なHTMLはどう処理されるか?
実務でフロントエンドをやっていると、CMSが出力したクソみたいなHTMLや、他部署が適当にコピペしてきたマークアップに遭遇して絶望することがあるよね。
「タグが閉じられてない!」「`
HTML5の仕様(Error Handling)では、「ブラウザは不正なHTMLを決してエラーにしてはならない。必ずリカバリしてDOMを構築せよ」という鉄の掟がある。
ブラウザの優しさが裏目に出る瞬間
例えば、次のような「閉じ忘れ」のHTMLがあったとする。
段落1のテキスト
段落2のテキスト
W3Cの仕様に基づき、ブラウザのパーサは次のように暗黙的な補完(Implied End Tags)を行う。
1. `
` の中に `
2. そのため、パーサは「おっと、ここで前の `
` が暗黙的に閉じられたな」と勝手に判断し、仮想的な `
` を挿入する。
3. その結果、DOMツリー構造は次のように歪められる。
- P
- #text: “段落1のテキスト”
- DIV
- #text: “ここにブロック要素がいきなり来た!”
- P
- #text: “段落2のテキスト”
「ブラウザが勝手に直してくれるからラッキー!」と思ってはいけない。この「ブラウザの勝手な推測(Fuzzy Parsing)」こそが、環境によってレイアウトが崩れたり、DOM操作系ライブラリ(ReactやVueなどのVirtual DOM差分アルゴリズム)が「あれ、思ってたツリーと違うぞ?」とバグる最大の原因になる。
—
3. 実践:JavaScriptでDOM構築の挙動をハックする
百聞は一見にしかず。ブラウザがいかにシビアに、そしてダイナミックにDOMを組み立てているかを、現代のフロントエンドの現場で使えるきれいなコードで体感してみよう。
以下のコードは、外部の怪しいHTMLスニペットを安全に(かつDOM構築のライフサイクルを意識しながら)パースし、親しみやすい安全なノードとして現在のドキュメントにマウントする実践的なユーティリティだ。
/
- 与えたHTML文字列を安全にパースし、DOMノードのフラグメントとして返却する
- @param {string} htmlString – パース対象のHTML文字列
- @returns {DocumentFragment} 構築されたDOMフラグメント
/
export function createSafeDOMFragment(htmlString) {
// 1. DOMParser APIのインスタンスを生成
// これにより、現在のメインDOMを汚さずに独立したパーサコンテキストでHTMLを解析できる
const parser = new DOMParser();
// 2. parseFromStringでHTMLをパース
// 第2引数に ‘text/html’ を指定することで、HTML5の寛大なエラーハンドリング仕様が適用される
const doc = parser.parseFromString(htmlString, ‘text/html’);
// 3. パース時に発生した致命的なエラー(構文解析エラーなど)をチェック
// DOMParserはパースエラーがある場合、
const parserError = doc.querySelector(‘parsererror’);
if (parserError) {
console.warn(
‘[DOM Parser Warning] HTMLの解析中に不正な構文が検出されましたが、ブラウザによりリカバリされました。’,
parserError.textContent
);
}
// 4. DocumentFragmentを使って、一括してDOMツリーを構築・アタッチする準備をする
// メモリ上でツリーを組み立てることで、余計なReflow(再レイアウト)を防ぐのがプロの技だ
const fragment = document.createDocumentFragment();
// body要素配下に構築されたDOMノードをフラグメントへ移し替える
// childNodesを直接ループしてmoveさせる
while (doc.body.firstChild) {
fragment.appendChild(doc.body.firstChild);
}
return fragment;
}
// — 【使用例】 —
// 不正なタグが含まれているHTMLをあえて渡してみる
const rawHtml = `
- 1. バイト列からDOMへ:レンダリングエンジンの裏側の舞台裏
- トークン化の狂気:状態機械(State Machine)
- 2. 驚異の「エラー耐性」:不正なHTMLはどう処理されるか?
- ブラウザの優しさが裏目に出る瞬間
- 3. 実践:JavaScriptでDOM構築の挙動をハックする
- タイトルです ここから段落ですがタグが閉じられていません。 インライン要素がはみ出しています。 `; // 実務のコンポーネント内での処理を想定 const container = document.getElementById(‘app-container’); if (container) { const safeFragment = createSafeDOMFragment(rawHtml); // 一発でDOMに反映(ブラウザの最適化が最も効く瞬間) container.appendChild(safeFragment); console.log(‘DOMツリーの構築が完了し、無事にマウントされました。’); } シニアからのTips:なぜ `DOMParser` を使うべきなのか?
タイトルです ここから段落ですがタグが閉じられていません。 インライン要素がはみ出しています。 `; // 実務のコンポーネント内での処理を想定 const container = document.getElementById(‘app-container’); if (container) { const safeFragment = createSafeDOMFragment(rawHtml); // 一発でDOMに反映(ブラウザの最適化が最も効く瞬間) container.appendChild(safeFragment); console.log(‘DOMツリーの構築が完了し、無事にマウントされました。’); } シニアからのTips:なぜ `DOMParser` を使うべきなのか?
よくあるアンチパターンとして、`element.innerHTML = dirtyHtml` とやってしまうケースがある。これだと、
1. 不必要な既存DOMの破棄と再構築コストが発生する。
2. もし `dirtyHtml` に悪意のあるスクリプトが含まれていた場合、XSS(クロスサイト・スクリプティング)の脆弱性に直結する(※ブラウザは `

コメント