Справочник · JavaScript
innerHTML: HTML-содержимое элемента
Свойство innerHTML возвращает HTML-разметку внутри элемента строкой, а при записи разбирает строку как HTML и заменяет всё содержимое элемента. Это удобно для своей готовой разметки, но опасно для данных от пользователя: вставленная строка может выполнить чужой код (уязвимость XSS). Для текста используют textContent.
- Чтение:
el.innerHTML— разметка внутри элемента строкой. - Запись заменяет всё содержимое и разбирает строку как HTML.
- Данные пользователя через innerHTML — уязвимость XSS; для текста бери
textContent. - Замена содержимого уничтожает старые элементы и их обработчики событий.
- Добавить, не стирая, —
insertAdjacentHTML('beforeend', html).
Минимальный пример
<div class="notice"><p>Старый текст</p></div>const box = document.querySelector('.notice');
console.log(box.innerHTML); // '<p>Старый текст</p>'
box.innerHTML = '<p><strong>Готово!</strong> Изменения сохранены.</p>';При чтении получаем строку с тегами. При записи браузер разбирает строку, создаёт элементы p и strong и ставит их вместо прежнего содержимого.
Почему innerHTML опасен: XSS
// имя пришло из поля формы или адресной строки
const name = '<img src=x onerror="alert(document.cookie)">';
greeting.innerHTML = `Привет, ${name}!`; // ОПАСНО: код выполнится
greeting.textContent = `Привет, ${name}!`; // безопасно: покажется как текстЕсли в строку попадает текст от пользователя — имя, комментарий, параметр из адреса, — злоумышленник может подставить разметку с обработчиком вроде onerror. Браузер выполнит этот код на твоей странице с доступом к её данным. Это межсайтовый скриптинг (XSS), одна из самых частых уязвимостей. Теги <script>, вставленные через innerHTML, не выполняются, но атрибуты-обработчики — выполняются.
Правило простое: данные пользователя — только через textContent или создание элементов. Если HTML от пользователя действительно нужен (редактор с форматированием), его очищают проверенной библиотекой-санитайзером, например DOMPurify, а лучше — на сервере.
innerHTML, textContent и innerText
| Свойство | Читает | Записывает | Безопасно для данных |
|---|---|---|---|
innerHTML | разметку внутри | разбирает как HTML | нет |
textContent | весь текст, включая скрытый | текст как есть | да |
innerText | видимый текст с учётом CSS | текст, переносы строк — в br | да |
outerHTML | разметку вместе с самим элементом | заменяет весь элемент | нет |
innerText учитывает CSS (не возвращает скрытый текст), поэтому браузеру приходится пересчитывать раскладку — это медленнее. Для записи текста в большинстве случаев подходит textContent.
Замена содержимого стирает обработчики
const button = list.querySelector('button');
button.addEventListener('click', onClick);
list.innerHTML += '<li>Новая задача</li>'; // старая кнопка заменена копией — обработчика нетЗапись в innerHTML — даже через += — разбирает всю разметку заново и создаёт новые элементы. Старые узлы вместе с обработчиками событий, введённым в поля текстом и состоянием исчезают. Кроме того, += в цикле каждый раз перестраивает весь список. Для добавления используй append или insertAdjacentHTML, а обработчики вешай делегированием на родителя.
insertAdjacentHTML: добавить, не стирая
list.insertAdjacentHTML('beforeend', '<li>В конец списка</li>');
list.insertAdjacentHTML('afterbegin', '<li>В начало списка</li>');
title.insertAdjacentHTML('afterend', '<p>Абзац после заголовка</p>');| Позиция | Куда вставит |
|---|---|
beforebegin | перед самим элементом |
afterbegin | внутрь, в начало |
beforeend | внутрь, в конец |
afterend | после самого элемента |
Существующие элементы не трогаются. Правило безопасности то же: данные пользователя в строку не подставляй.
Безопасный способ собрать разметку
function renderLesson(lesson) {
const li = document.createElement('li');
const link = document.createElement('a');
link.href = `/lessons/${encodeURIComponent(lesson.slug)}/`;
link.textContent = lesson.title; // текст, а не HTML
li.append(link);
return li;
}
list.replaceChildren(...lessons.map(renderLesson));Структуру создают через createElement, а данные кладут в textContent и свойства. Для больших повторяющихся блоков удобен тег template: разметка лежит в HTML, скрипт клонирует её и заполняет текстом.
Когда innerHTML уместен
innerHTML нормален, когда вся разметка твоя и в ней нет данных от пользователя: очистить контейнер (el.innerHTML = '', хотя понятнее el.replaceChildren()), вставить заранее написанный фрагмент, прочитать разметку для отладки. Как только в строку попадает что-то извне — переходи на textContent.
innerHTML или textContent
Текст, особенно от пользователя: имена, комментарии, результаты поиска. Безопасно и быстро.
Своя готовая разметка без внешних данных. С данными пользователя — риск XSS.
Частые ошибки
- Подставляют в innerHTML данные из формы, адреса или с сервера без очистки.
- Добавляют элементы через
innerHTML +=в цикле и теряют обработчики. - Используют innerHTML только чтобы поменять текст, хотя есть textContent.
- Ждут, что вставленный через innerHTML тег script выполнится.
- Очищают ненадёжный HTML самописной заменой символов вместо проверенного санитайзера.
Доступность
Замена всего содержимого через innerHTML сбрасывает фокус и позицию скринридера, если человек был внутри этого блока. Для обновления части интерфейса меняй только нужные узлы, а о результате сообщай через область с aria-live="polite". Вставляемая разметка должна быть такой же семантичной, как написанная в HTML: кнопки — button, списки — ul.
Поддержка браузерами
innerHTML, outerHTML, textContent и insertAdjacentHTML работают во всех браузерах. replaceChildren поддерживается всеми актуальными браузерами.
Практика
Есть список ul.comments и массив комментариев { author, text } от пользователей. Выведи комментарии безопасно, без innerHTML с данными: имя автора жирным, текст обычным абзацем. Затем добавь в конец списка служебную строку «Конец комментариев» через insertAdjacentHTML.
Показать решение
const list = document.querySelector('.comments');
for (const comment of comments) {
const li = document.createElement('li');
const author = document.createElement('strong');
author.textContent = comment.author;
const text = document.createElement('p');
text.textContent = comment.text;
li.append(author, text);
list.append(li);
}
list.insertAdjacentHTML('beforeend', '<li class="end">Конец комментариев</li>');Частые вопросы
Чем innerHTML отличается от textContent?
innerHTML разбирает строку как HTML и создаёт элементы, textContent вставляет текст как есть. Для текста и данных пользователя используй textContent.
Почему innerHTML небезопасен?
Если в строку попадают данные пользователя, в них можно спрятать разметку с обработчиком (например, onerror), и браузер выполнит чужой код. Это уязвимость XSS.
Выполнится ли script, вставленный через innerHTML?
Нет, теги script через innerHTML не выполняются. Но атрибуты-обработчики вроде onerror и onclick — выполняются, поэтому опасность остаётся.
Почему после innerHTML += перестали работать кнопки?
Запись пересоздаёт все элементы внутри, и старые обработчики теряются. Добавляй элементы через append или insertAdjacentHTML и используй делегирование.
Как очистить элемент?
el.replaceChildren() или el.innerHTML = ''. Первый вариант нагляднее.