JavaScript / 38 минут

DOM и построение интерфейса

Стройте DOM-деревья, безопасно вставляйте текст и обновляйте интерфейс.

В процессе Теория: впереди Практика: впереди Тест: впереди
17 / 22
После этой главы
  • Стройте DOM-деревья, безопасно вставляйте текст и обновляйте интерфейс.
  • Применить тему в задаче «DOM и построение интерфейса» и объяснить граничные случаи.

Около 8 минут на чтение и разбор примеров. Практика — отдельно, в своём темпе.

Документ как дерево узлов

Браузер разбирает HTML и строит Document Object Model — объектное представление документа. Элементы, текст и другие узлы образуют дерево. JavaScript получает API для поиска узлов и изменения этого дерева, а браузер затем отображает результат. Исходный HTML-файл и текущее DOM-дерево не обязаны совпадать: скрипт мог добавить элементы, пользователь изменить поле, а библиотека перестроить часть интерфейса.

DOM не является частью грамматики JavaScript. В обычном Node.js document отсутствует, если специальная среда его не предоставила. Поэтому практику этой главы мы проверяем с помощью Linkedom — отдельной реализации нужных DOM-операций внутри серверной песочницы. Это позволяет проверять создание узлов и текст, но не имитирует всю отрисовку настоящего браузера.

Поиск элементов и возможное отсутствие

querySelector возвращает первый элемент, соответствующий CSS-селектору, либо null. querySelectorAll возвращает коллекцию найденных элементов. Выбор первого результата не доказывает уникальность элемента. Если в документе несколько одинаковых кнопок, слишком общий селектор может управлять не той частью интерфейса. Ограничивайте поиск корневым элементом нужного компонента.

JavaScript
const list = document.querySelector('#courses');
if (list === null) throw new Error('Не найден список курсов');
const items = list.querySelectorAll('li');
console.log(items.length);

Проверка null нужна не только для компилятора TypeScript. Она выражает решение о том, что отсутствие элемента означает для программы. Если элемент необязательный, можно пропустить обновление. Если без него интерфейс неработоспособен, полезнее понятная диагностика, чем молчаливое игнорирование. Также учитывайте момент запуска скрипта: нужная часть HTML должна быть уже разобрана.

Создание узлов и безопасный текст

createElement создаёт элемент, который ещё не вставлен в документ. После настройки его добавляют в нужного родителя через append или другое подходящее API. textContent помещает строку как текст, а не разбирает её как HTML. Это важное свойство для пользовательского ввода и содержимого внешнего API.

JavaScript
const item = document.createElement('li');
item.textContent = '<strong>Это обычный текст</strong>';
list.append(item);

В этом примере пользователь увидит угловые скобки, а не жирное начертание. innerHTML предназначен для разбора разметки и требует другой модели доверия и очистки. Не создавайте HTML из случайной строки с именем пользователя только потому, что это экономит две инструкции. Если структура известна, создавайте её узлами и передавайте текст отдельно.

Свойства, атрибуты и классы

Атрибут HTML и свойство DOM-объекта связаны, но не всегда эквивалентны. Например, текущее value поля ввода может измениться после действий пользователя, тогда как исходный атрибут описывал первоначальное значение. Для чтения пользовательского ввода обычно нужно свойство элемента. Для произвольного data-атрибута удобно использовать dataset с пониманием, что значения там строковые.

JavaScript
const button = document.createElement('button');
button.type = 'button';
button.textContent = 'Сохранить';
button.classList.add('button');
button.dataset.action = 'save';
button.disabled = false;

classList позволяет добавлять, удалять и переключать отдельный класс без ручного склеивания всей строки className. Состояние disabled задают свойством boolean, а не строкой 'false' в атрибуте: присутствие булевого атрибута имеет собственную HTML-семантику. Проверяйте, является ли выбранный атрибут обычным текстовым или булевым.

Обновление, перемещение и производительность

Один узел не может одновременно занимать два места дерева. Если append получает существующий узел, он перемещается. Для независимой копии существует cloneNode, но копирование разметки не означает автоматическое копирование всех подписок и текущего состояния. Лучше явно определить, какие данные являются источником истины, и построить интерфейс из них.

JavaScript
function renderNames(root, names) {
  const fragment = document.createDocumentFragment();
  for (const name of names) {
    const row = document.createElement('li');
    row.textContent = name;
    fragment.append(row);
  }
  root.replaceChildren(fragment);
}

Пакетная подготовка узлов помогает избежать хаотичных промежуточных обновлений. Однако самая важная оптимизация часто состоит в понятной модели данных и отсутствии лишних повторных действий. Не чередуйте массовые записи DOM с измерениями геометрии без необходимости: такие операции могут заставлять браузер пересчитывать отображение чаще ожидаемого.

Практика и проверка результата

В задании нужно создать li для каждой входной строки и вернуть прочитанные textContent в исходном порядке. Проверяется настоящая структура документа, а не ручная сборка массива правильных ответов. Вход '<b>текст</b>' должен остаться тем же текстом: если получится просто 'текст', значит строка была неверно интерпретирована как разметка.

Проверьте пустой список, пустое название, Unicode и несколько элементов. Создавайте новый документ при каждом вызове solve, чтобы результаты разных проверок не смешивались. Для самостоятельного проекта добавьте удаление элемента и объясните, где хранится исходный массив данных. Затем обеспечьте доступность: используйте настоящую кнопку для действия, label для поля и понятное сообщение об изменении состояния.

Загружаем редактор…
Следующая темаСобытия и формы

Музыка: Reverie — Scott Buckley · CC BY 4.0