3秒でわかる
要素の中身を HTML 文字列として読み書きするプロパティ。手軽な反面、外部由来の文字列を渡すと XSS の入口になります。
もう少し詳しく
どういうものか
DOM 要素が持つプロパティで、その要素の内側にある HTML をタグごと文字列として取り出したり、文字列を代入して中身を丸ごと差し替えたりできる。代入された文字列はブラウザによって解析され、実際の要素として組み立てられる。
タグを含む構造をまとめて作れるため、createElement を何度も呼ぶより記述が短くなる。
なぜ必要か
複数の要素からなるかたまりを作るとき、DOM API だけで書くと、要素を作り、属性を設定し、親に追加する手順が要素の数だけ並ぶ。innerHTML なら、できあがりの形をそのまま書ける。テンプレートリテラルと組み合わせると、繰り返しの描画が見通しよくなる。
具体例
const items = [
{ id: 1, name: "コーヒー", price: 480 },
{ id: 2, name: "紅茶", price: 420 },
];
// 中身を丸ごと差し替える
document.querySelector("#list").innerHTML = items
.map((it) => `<li data-id="${it.id}">${it.name} ${it.price}円</li>`)
.join("");
// 読み取ることもできる
console.log(document.querySelector("#list").innerHTML);外部から来た値を混ぜる場合は、代入先を分けて textContent を使う。
const li = document.createElement("li");
li.textContent = userInput; // タグとして解釈されない
list.appendChild(li);つまずきやすいところ
最大の問題が XSS になる。利用者の入力や API の応答をそのまま innerHTML に渡すと、 のような文字列がスクリプトとして動く。 タグは innerHTML 経由では実行されないため安全だと誤解されがちだが、onerror や onload といったイベント属性は普通に発火する。外部由来の値は textContent に入れるか、エスケープを通す。
イベントリスナーが消える点も見落とされやすい。innerHTML で中身を書き換えると、元の要素は破棄されて作り直されるため、addEventListener で登録したハンドラは失われる。親要素に 1 つだけ登録して event.target で判定する委譲の書き方にすると影響を受けない。
+= での追記も避けたい。既存の中身を文字列化してから全体を作り直すため、要素が増えるほど重くなり、入力途中の値や選択状態も失われる。
似た用語との違い
| プロパティ | 中身の扱い | タグの解釈 |
|---|---|---|
innerHTML | 内側の HTML | する |
outerHTML | 自分自身を含む HTML | する |
textContent | 文字列のみ | しない |
