〇背景
前回 DOM を学んで、「DOM は画面の実体(木)、HTML は設計図(文字列)」までは腹落ちした。
では querySelector
や appendChild
みたいな「DOMの関数」はどこで定義され、誰が実行しているのか?――ここを整理して、onMounted
の理解にもつなげる。
〇今回のコード例(最小の全体像)
<!doctype html>
<html>
<body>
<ul id="list"></ul>
<!-- 「設計図」=HTMLを読み終えた後、JSが実行される(defer) -->
<script defer>
// ① DOM は既にできている(#list を取れる)
const ul = document.querySelector('#list')
// ② JS(ブラウザのJSエンジン)が「DOMの関数」を呼ぶ
const frag = document.createDocumentFragment()
for (const label of ['A','B','C']) {
const li = document.createElement('li') // ← DOM API(Element作成)
li.textContent = label // ← DOM API(テキスト)
frag.appendChild(li) // ← DOM API(木に接ぎ木)
}
ul.appendChild(frag) // ← まとめて挿入(高速)
</script>
</body>
</html>
ポイント:実体を変えるのは常に“JSの実行”。
HTML(設計図)は DOM を作るまで、DOM API を呼ぶのはJS。
〇調べる前の自分の認識
「ブラウザが関数を知っていて、設計書(HTML)をトリガーに DOM 側で実行される?」
→ 惜しい。関数は“ブラウザが提供” しているが、それを“呼ぶ”のは JS。
〇調べた結果(要点)
-
誰が何を持ってる?
-
JavaScript(ECMAScript) … 言語そのもの(
if
,for
, 関数、Promise など) -
DOM/HTML/CSSOM などのWeb API … ブラウザが提供するオブジェクトと関数群(
document
,Element
,querySelector
,appendChild
,innerHTML
…)
-
JavaScript(ECMAScript) … 言語そのもの(
-
どう実行される?
- ブラウザが HTML をパース → DOM(木) を作る
-
<script>
に到達 → JSエンジンが JS を実行- 実行中に DOM API を呼べば 実体のDOMが変わる(=画面が変わる)
JSが、ブラウザが持っておるDOM関数を呼ぶ
とり合えず今はブラウザの中にJSエンジンなるものがあるという認識でおけ。
役割の地図(超チートシート)
領域 | 例 | 役割 |
---|---|---|
HTML | <ul id="list"> |
構造の設計図(文字列)。パースされて初期DOMに |
DOM API | document.createElement |
ブラウザ提供の関数(オブジェクトの木を操作) |
JavaScript |
for , () => {}
|
関数を“呼ぶ”実行コード |
CSSOM |
element.style , getComputedStyle
|
見た目のモデル。DOMと組み合わせて描画へ |
〇動作解説(図解やコメント付きコード)
1) パイプラインの全体像(図解)
[HTML文字列]
↓ (HTMLパーサ)
[DOMツリー] ←←←←←← ここが「画面の実体」
↓ ↑
< script > 到達 │(JSから DOM API を呼ぶ)
↓ │
[JSエンジンが実行] ─┘ 例:document.querySelector / appendChild ...
↓
[DOMが変わる] → [レイアウト/ペイント] → 画面が更新
2) HTMLが“実行”しないことの証明
<ul id="list">
<!-- ここに <li> は最初ない -->
</ul>
<script>
// JSを消せば、何も起きない(HTMLは設計図だけ)
document.querySelector('#list').innerHTML = '<li>JSで追加</li>'
</script>
3) HTMLから直接ハンドラを書く vs JSで登録
<button id="b1" onclick="alert('NG寄り:HTMLがJSを内蔵')">古い書き方</button>
<button id="b2">推奨:JS側でイベント登録</button>
<script>
document.getElementById('b2').addEventListener('click', () => {
alert('イベントはJS側に集約')
})
</script>
実務は addEventListener 一択(関心の分離・テストしやすさ・XSS耐性)
4) DocumentFragment と <template>
(高速&宣言的)
<template id="row"><li class="item">placeholder</li></template>
<ul id="list"></ul>
<script>
const t = document.getElementById('row')
const frag = document.createDocumentFragment()
for (const label of ['A','B','C']) {
const node = t.content.firstElementChild.cloneNode(true)
node.textContent = label
frag.appendChild(node) // ← 仮置き場に貯める
}
document.querySelector('#list').appendChild(frag) // ← 一撃で挿入(速い)
</script>
5) いつJSが走るの?(defer
と async
と type="module"
)
<!-- 既定:パースを止めて即実行(ブロッキング)→ 避けたい -->
<script src="app.js"></script>
<!-- 推奨:HTMLパース完了後に順番通り実行(DOMが出来ている) -->
<script src="app.js" defer></script>
<!-- async:読めた順に即実行(順序保証なし、主に計測系) -->
<script src="tag.js" async></script>
<!-- ES Modules:自動defer + import/export が使える -->
<script type="module" src="main.js"></script>
いつ使うねん
〇実務での注意点
-
DOM操作はJSで、HTMLは宣言に徹する:
onclick
直書きは避け、addEventListener
を使う。 -
ロード順を管理:
defer
ortype="module"
を基本に。async
は副作用が薄いスクリプト専用。 -
textContent
優先:表示はtextContent
、どうしてもinnerHTML
ならサニタイズ(XSS対策)。 -
まとめて挿入:多数ノードは
DocumentFragment
/<template>
でまとめて。 -
レイアウトスラッシング回避:測定→変更の順でバッチ。必要なら
requestAnimationFrame
。 -
イベント委譲:動的リストは親で1本受けて
event.target
判定(パフォーマンス・解放漏れ防止)。 -
属性 vs プロパティ:フォームは基本 プロパティ(
input.value
) を触る。setAttribute
は初期状態向け。 - 環境差:Web Worker / Node.js / React Native は DOMなし。DOM API 前提のコードは動かない。
- SPAでは“直接DOM”を控える:Vue/React では状態→UIの流れを優先(必要最小限に絞る)。
〇まとめ・所感
- HTML = 設計図(文字列)、DOM = 実体(木)、DOMの関数 = ブラウザ提供のWeb API、それを呼ぶのがJS。
- 「設計図が関数を実行する」のではなく、JSが関数を呼び出して実体(DOM)を変える。
-
onMounted
の理解にも直結:**「DOMができた後にJSで初期化/取得/接ぎ木」**をするフェーズ、ということ。
JSエンジンが池の近くに住む庭師で、DOM関数が池のほとりにある接ぎ木や庭師道具って感じかな。絵にたまに庭師への指示が書かれてあると考えたほうが良い。
正直このDOM関数に関してはもっと機能ごとに掘り下げが出来るが、あまりに量が多くなるので、このVueシリーズではしないようにする。今度別の単発記事でまとめてみたいと思う。DOMについての深堀はいったんここまでにしよう。だいぶ理解が進んだ。