JavaScript実践 #1 DOM操作の基本
JavaScriptトラックの最後 — 実践シリーズです。基礎/中級/上級で鍛えたツールを束ねてバニラJavaScriptで動的なWebページを作ります。
全6編で構成されます。
- #1 DOM操作の基本 ← 今回の記事
- #2 イベントハンドリングと委譲
- #3 fetch と非同期 UI
- #4 フォームを扱う — validation、FormData
- #5 ローカルストレージと軽量な状態管理
- #6 小さなアプリのビルド — Todo またはライブ検索
今回の記事では DOMを触る基本 を扱います — ページのエレメントを探し、内容/属性/クラスを変え、新しいエレメントを作って差し込む場面です。
DOMとは? #
DOM (Document Object Model) は、ブラウザがHTMLをJavaScriptが扱えるオブジェクトツリーに変換したものです。<h1>、<p> のようなタグがすべてJavaScriptオブジェクトになります。
<body>
<h1 id="title">こんにちは</h1>
<p class="message">本文</p>
</body>これをJavaScriptから見ると、次のようなツリー構造になります。
document
└─ html
└─ body
├─ h1#title "こんにちは"
└─ p.message "本文"document がエントリポイントで、ツリー構造ですべてのエレメントにアクセスできます。
エレメントを探す — querySelector ファミリー
#
最もよく使うメソッド。
// CSS セレクタ — 1個
const title = document.querySelector('#title');
const firstP = document.querySelector('p');
const greenBtn = document.querySelector('.btn.green');
// CSS セレクタ — 複数 (NodeList)
const allP = document.querySelectorAll('p');
const items = document.querySelectorAll('.item');CSSセレクタをそのまま使えるので直感的です。昔は getElementById、getElementsByClassName、getElementsByTagName が別々にありましたが、モダンなコードではほぼquerySelectorだけ使います。
querySelectorAll の NodeList — 配列ではない
#
const items = document.querySelectorAll('.item');
items.length; // OK — 長さあり
items.forEach(...); // OK — forEach 可能
items.map(...); // ✗ map なし
items.filter(...); // ✗ filter なし
// 本物の配列にするには
const arr = [...items]; // OK — spread
const arr2 = Array.from(items); // OK
forEach はNodeList自体にありますが、map/filter/reduce はありません。配列メソッドが必要ならspreadまたは Array.from で変換します。
親/子/兄弟 — ツリー探索 #
const el = document.querySelector('.item');
// 親
el.parentElement;
el.parentNode; // ほぼ同じ (drift ケースあり)
// 子
el.children; // HTMLCollection — 要素のみ
el.childNodes; // NodeList — テキスト/コメント含む
el.firstElementChild;
el.lastElementChild;
// 兄弟
el.nextElementSibling;
el.previousElementSibling;
// 最も近い祖先 (自分を含む)
el.closest('.container');closest はよく使います — イベント委譲 (#2) パターンの核心です。
内容を変える — textContent vs innerHTML
#
const el = document.querySelector('#title');
// テキストのみ — 安全
el.textContent = '新しいタイトル';
// HTML パース — 危険な可能性
el.innerHTML = '<strong>新しいタイトル</strong>';textContent はテキストとしてのみ入ります。ユーザー入力や外部データを入れても安全です。
innerHTML はHTMLとしてパースされます。<strong> タグが本物の強調としてレンダリングされますが、信頼できないデータを入れるとXSS攻撃に脆弱になります。
const userInput = '<img src=x onerror="alert(1)">';
el.textContent = userInput; // 安全 — ただの文字列
el.innerHTML = userInput; // ✗ スクリプトが実行される
基本は textContent。HTMLが本当に必要なときだけ、その場で innerHTML を慎重に使ってください。
属性を扱う #
一般属性 #
const link = document.querySelector('a');
link.href; // 直接プロパティでアクセス
link.href = 'https://...';
link.getAttribute('href'); // 同じ
link.setAttribute('href', 'https://...');
link.removeAttribute('href');
link.hasAttribute('target'); // true/false
href、src、id のようなありふれた属性は直接ドットアクセスのほうが短いです。よく知られていない属性や動的キーは getAttribute/setAttribute。
データ属性 — data-*
#
HTMLにJavaScript用のデータを埋め込むときに活用します。
<button data-id="42" data-action="delete">削除</button>const btn = document.querySelector('button');
btn.dataset.id; // '42' (常に string)
btn.dataset.action; // 'delete'
btn.dataset.id = '100'; // 属性更新 → HTML も変わる
camelCase変換に注意 — HTMLの data-user-name はJSで dataset.userName としてアクセスします。
クラスを扱う — classList
#
const el = document.querySelector('.box');
el.classList.add('active');
el.classList.remove('hidden');
el.classList.toggle('open'); // あれば外し、なければ追加
el.classList.toggle('disabled', isDisabled); // 第二引数で強制
el.classList.contains('active'); // boolean
el.classList.replace('a', 'b');classList はモダンなJavaScriptの標準ツールです。昔の el.className = 'a b c' (文字列で直接扱う) よりも安全で可読性も良いです。
スタイルを扱う #
インラインスタイル #
const el = document.querySelector('.box');
el.style.color = 'red';
el.style.backgroundColor = '#eee'; // CSS の background-color は JS で camelCase
el.style.transform = 'translateX(10px)';ただしインラインスタイルは乱用しないでください。CSSクラスをトグルするほうがほぼ常にすっきりします。
el.classList.add('error');
// CSS で .error { color: red; ... } を定義
CSS 変数を扱う #
CSSカスタムプロパティ (--var) は setProperty で動的に変えられます。
el.style.setProperty('--main-color', '#ff5733');
const value = getComputedStyle(el).getPropertyValue('--main-color');テーマ変更や動的スタイリングの場面に向いています。
エレメントを作って差し込む #
新しいエレメントを作る #
const div = document.createElement('div');
div.className = 'item';
div.textContent = '新しい項目';差し込む — append / prepend / before / after
#
const parent = document.querySelector('.list');
const item = document.createElement('li');
parent.append(item); // 末尾に追加
parent.prepend(item); // 先頭に追加
existingChild.before(item); // 既存の子の前に
existingChild.after(item); // 既存の子の後ろに
existingChild.replaceWith(item); // 子を置き換え
古いメソッド (appendChild、insertBefore) も動作しますが — 新しいメソッドのほうが短く直感的なので、モダンなコードではこちらを使います。
一度に複数のエレメント #
parent.append(li1, li2, li3);
parent.append('テキストも可能', divEl, '\n');文字列も一緒に受け取って自動的にテキストノードに変換してくれます。
削除する — remove
#
el.remove();古いコードの el.parentNode.removeChild(el) よりずっと短いです。
効率 — DocumentFragment
#
複数のエレメントを一度に追加するとき、ツリーに挿入するたびにreflowが起こると高くつきます。DocumentFragment が一時的なコンテナの役割をしてくれます。
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
const li = document.createElement('li');
li.textContent = `Item ${i}`;
fragment.append(li); // fragment にいったん集める
}
list.append(fragment); // 一度だけ本物のツリーに挿入 — reflow 1回
大きな差は100個程度から見えます。ほとんどの場面では意識する必要がありませんが、大きなリストを描くときに知っておくと良いです。
innerHTML + テンプレートリテラル — 短いが慎重に
#
list.innerHTML = items
.map((item) => `<li class="item">${item.name}</li>`)
.join('');これは非常に短いですが — ${item.name} がユーザー入力ならXSSリスクがあります。信頼できるデータでのみ、またはescape処理を経た後で使ってください。
エレメント情報 #
el.tagName; // 'DIV' (大文字)
el.id;
el.className; // 文字列 — 推奨は classList
el.getBoundingClientRect(); // 画面上の位置/サイズ
el.offsetWidth;
el.offsetHeight;
el.offsetTop;
el.offsetLeft;
el.scrollTop;
el.scrollLeft;レイアウトやスクロールを扱う場面でよく出会います。
closest の活用
#
上のツリー探索で少し見たメソッド。これがイベントハンドリングで非常によく使われるので、もう一度整理。
list.addEventListener('click', (e) => {
const item = e.target.closest('.item');
if (!item) return;
const id = item.dataset.id;
// ...
});リスト内のどこをクリックしても、最も近い .item を見つけて id を取り出します。次回の記事で本格的に扱うイベント委譲パターンの基盤です。
まとめ #
今回の記事で整理した内容。
- DOMはHTMLをオブジェクトツリーにしたJavaScriptのインターフェース
querySelector/querySelectorAllがモダンな標準- NodeListは配列ではない — 配列メソッドが必要ならspread/Array.from
textContentが安全、innerHTMLは信頼できるデータでのみdatasetでdata-*属性を扱うclassList.add/remove/toggleがクラスの標準append/prepend/before/afterがモダンな差し込み- 大きなリストは
DocumentFragmentで一度に closestで最も近い祖先を見つける
次回の記事 (#2 イベントハンドリングと委譲) では、イベントオブジェクト、バブリングとキャプチャリング、そして効率的なイベント委譲パターンを扱います。