textContent プロパティに文字列を代入すると、ブラウザはその文字列を「プレーンテキスト」として扱います。HTML として解釈されることはありません。
const div = document.createElement('div') div.textContent = '<script>alert("XSS")</script>'
この時点で、DOM ツリー上の div 要素の中身はテキストノードです。<script> タグとして解釈されることはなく、文字どおりの文字列として保持されます。
innerHTML で取り出すとエスケープされる理由
innerHTML は要素の中身を HTML 文字列として返します。テキストノードを HTML 文字列に変換する過程で、ブラウザは特殊文字を文字実体参照に変換します。
div.innerHTML // → '<script>alert("XSS")</script>'
これはブラウザの HTML シリアライズ処理によるものです。テキストノードの内容をそのまま HTML に埋め込むと意味が変わってしまうため、< は < に、> は > に自動変換されます。
textContent 自体はエスケープしない
誤解されやすい点ですが、textContent への代入時にエスケープが起きているわけではありません。
const div = document.createElement('div') div.textContent = '<b>test</b>' div.textContent // → '<b>test</b>'(そのまま) div.innerHTML // → '<b>test</b>'(エスケープ済み)
textContent で読み取ると元の文字列がそのまま返り、innerHTML で読み取るとエスケープされた文字列が返ります。エスケープは「HTML 文字列として出力する」段階で発生します。
実用上の注意点
この手法はブラウザ環境でのみ動作します。Node.js では DOM API が存在しないため、文字列置換やライブラリを使う必要があります。また、DOM 操作を伴うためパフォーマンスが重要な場面では純粋な文字列置換のほうが高速です。