textContent と HTML エスケープの仕組み

textContent プロパティに文字列を代入すると、ブラウザはその文字列を「プレーンテキスト」として扱います。HTML として解釈されることはありません。

const div = document.createElement('div')
div.textContent = '<script>alert("XSS")</script>'

この時点で、DOM ツリー上の div 要素の中身はテキストノードです。<script> タグとして解釈されることはなく、文字どおりの文字列として保持されます。

innerHTML で取り出すとエスケープされる理由

innerHTML は要素の中身を HTML 文字列として返します。テキストノードを HTML 文字列に変換する過程で、ブラウザは特殊文字を文字実体参照に変換します。

div.innerHTML
// → '&lt;script&gt;alert("XSS")&lt;/script&gt;'

これはブラウザの HTML シリアライズ処理によるものです。テキストノードの内容をそのまま HTML に埋め込むと意味が変わってしまうため、<&lt; に、>&gt; に自動変換されます。

textContent 自体はエスケープしない

誤解されやすい点ですが、textContent への代入時にエスケープが起きているわけではありません。

const div = document.createElement('div')
div.textContent = '<b>test</b>'

div.textContent  // → '<b>test</b>'(そのまま)
div.innerHTML    // → '&lt;b&gt;test&lt;/b&gt;'(エスケープ済み)

textContent で読み取ると元の文字列がそのまま返り、innerHTML で読み取るとエスケープされた文字列が返ります。エスケープは「HTML 文字列として出力する」段階で発生します。

実用上の注意点

この手法はブラウザ環境でのみ動作します。Node.js では DOM API が存在しないため、文字列置換やライブラリを使う必要があります。また、DOM 操作を伴うためパフォーマンスが重要な場面では純粋な文字列置換のほうが高速です。