DOM: страница как дерево, которое можно менять
Зачем:Браузер хранит страницу как дерево, и JavaScript может в нём искать, читать и менять. После урока вы находите элементы селекторами, создаёте новые из данных и понимаете, почему чужой текст нельзя вставлять через innerHTML.
25 минут
Озвучено синтезом речи (Qwen3-TTS), голос не принадлежит человеку.
DOM — объектная модель документа: дерево узлов, которое браузер строит из HTML. JavaScript находит в нём элементы, меняет их и создаёт новые.
Страница, которую можно потрогать
В уроке про рендеринг вы видели, что браузер читает HTML и строит из него DOM (Document Object Model, объектную модель документа): дерево, в котором каждый тег, каждый кусок текста и каждый атрибут становятся узлом. Страница на экране рисуется с этого дерева. Главное для нас: браузер отдаёт это дерево JavaScript. В скрипте оно доступно под именем document, и всё, что скрипт в нём поменяет, браузер тут же перерисует.
Так делается любая интерактивность: меню открывается, потому что скрипт убрал у элемента атрибут hidden; счётчик растёт, потому что скрипт записал в элемент новое число; список появляется, потому что скрипт создал для каждого пункта элемент и вставил его на страницу. Три действия: найти, прочитать или изменить, создать и вставить.
Найти элемент
Для поиска в языке есть две команды, и в них те же селекторы, что вы писали в CSS: h3, .card, #offer, nav a.
const first = document.querySelector('.card'); // первый подходящий или null
const all = document.querySelectorAll('#offer .card'); // все подходящие querySelector возвращает один элемент или null, если ничего не нашлось. querySelectorAll возвращает список узлов (NodeList), возможно пустой. Его можно обойти через forEach или for...of, а чтобы пользоваться map и filter, превращают в массив: Array.from(all).
Из найденного элемента можно искать дальше: card.querySelector('h3') ищет только внутри этой карточки. Поэтому селектор можно писать короче, а скрипт получается аккуратнее.
Попробуйте селекторы на маленькой странице, и найденное подсветится и в разметке, и в дереве:
Ничего не найдено. querySelector вернул бы null, а querySelectorAll пустой список.
Проверьте себя: что вернёт querySelector, если элемента нет?
null. Поэтому, когда элемент может отсутствовать, результат проверяют, иначе следующая строка вроде el.textContent = ... упадёт с ошибкой.
Прочитать и изменить
Когда элемент найден, у него есть свойства и методы:
textContentчитает и записывает текст элемента:title.textContent = 'Новое название';getAttribute('href')иsetAttribute('href', '#top')работают с атрибутами. У многих атрибутов есть и короткие свойства:link.href,input.value,el.id,el.hidden;classList.add('selected'),classList.remove('selected'),classList.toggle('selected')управляют классами. Это лучший способ менять вид: скрипт навешивает класс, а CSS знает, что с ним делать. Так оформление остаётся в CSS, а логика в JS;datasetдаёт доступ к атрибутам видаdata-price="1200":card.dataset.price. Через них разметка передаёт скрипту свои данные.
Создать и вставить
Чтобы добавить новое, элемент сначала создают в памяти, настраивают, а потом вставляют в дерево:
const row = document.createElement('li'); // пока его нет на странице
row.textContent = 'Новый пункт';
list.append(row); // теперь есть Другие нужные команды: prepend (в начало), remove() (убрать элемент), replaceChildren(...) (заменить всё содержимое). Если вставляете много элементов подряд, собирайте их в памяти и вставляйте одним append с несколькими аргументами: браузеру не надо перерисовывать страницу после каждого.
Почему чужой текст нельзя вставлять через innerHTML
Есть и более короткий способ: свойство innerHTML. В него записывают строку с разметкой, и браузер её разбирает:
box.innerHTML = '<p>Привет, ' + name + '</p>'; Выглядит удобно, но если name пришёл от посетителя (из поля формы, из комментария, из ответа сервера, из адреса страницы), то в нём может быть не имя, а разметка с обработчиком события. Браузер не видит разницы между «вашей» разметкой и «чужой»: он разберёт всё и выполнит чужой код от имени вашего сайта. Такая уязвимость называется XSS (cross-site scripting, межсайтовое выполнение скриптов). Через неё воруют сессии и подменяют страницы.
Правило простое: чужой текст вставляют через textContent. Он записывает строку как текст, символы < и > остаются обычными буквами, и ничего не выполняется. innerHTML оставьте для разметки, которую целиком написали вы.
Проверьте на одной и той же строке два способа:
текст «<img src=x onerror=alert(1)>Анна»
Безопасно. Одна строка текста; на странице она появится буквами.
Здесь без тегов. Но защищает не строка, а привычка: чужой текст идёт через textContent.
Ничего из этого не выполняется: строка разбирается в изолированном документе, который никуда не вставляется.
Когда скрипт запускается
Ошибка, на которой спотыкаются все: скрипт ищет элемент, а его ещё нет. Обычный <script> останавливает разбор страницы и выполняется сразу, поэтому всё, что стоит в документе ниже, ему пока не видно. Выходов три: положить скрипт в самый конец страницы, добавить ему атрибут defer или сделать его модулем, как в нашем редакторе (type="module"). Модуль выполняется после разбора страницы, и искать в нём можно что угодно.
Практика
Сайт эпохи 2014 года. У вас уже есть список карточек, но на длинной странице их не видно с первого экрана. Пусть скрипт построит из карточек быстрый переход: ссылки на каждое предложение вверху секции, плюс счётчик.
Скрипт ничего не придумывает, он читает то, что есть в разметке: находит карточки, берёт заголовок каждой и делает ссылку на неё. Добавите в разметку ещё одну карточку, и ссылка появится сама. А если карточек нет, посетитель увидит понятное сообщение, а не пустое место.
Обратите внимание, чего нет в задании: слова innerHTML. Заголовок карточки может содержать что угодно, поэтому всё идёт через textContent.
Что было не так раньше
Когда-то DOM в разных браузерах был разным: у Internet Explorer одни методы, у Netscape другие, и document.all, document.layers нужно было угадывать. Стандарт появился в конце 90-х, а его последователи сначала неохотно его поддерживали. Именно из-за этой путаницы в 2006 году родилась библиотека jQuery: одна функция $('.card'), которая работала одинаково в любом браузере. На ней сделаны миллионы сайтов, и многие работают до сих пор, а если вы читаете чужой код со знаком доллара, то перед вами jQuery.
Сегодня браузеры ведут себя одинаково, стандарт называется DOM Standard (WHATWG), а querySelector, classList и append делают то, ради чего раньше ставили библиотеку. Для новых сайтов jQuery не нужен: он весит десятки килобайт и решает проблемы, которых больше нет. Правильным осталось другое: искать элементы по селекторам, менять классы, а не стили, и не доверять чужому тексту.
Практика
Готовлю вашу страницу…
Тест
Вопрос 1 из 7
Карточки
Эти карточки попадут в повторение, когда вы завершите урок.
Что такое DOM?
Ответ
Дерево объектов, которое браузер строит из HTML. JavaScript получает его как document и может искать, читать и менять узлы. Страница на экране перерисовывается следом.
Чем querySelector отличается от querySelectorAll?
Ответ
querySelector находит первый подходящий элемент или null. querySelectorAll возвращает список всех подходящих (возможно, пустой). Селекторы те же, что в CSS.
Как добавить на страницу новый элемент?
Ответ
Создать createElement, настроить (textContent, атрибуты, classList) и вставить в дерево через append. Пока не вставлен, на странице его нет.
Почему чужой текст нельзя вставлять через innerHTML?
Ответ
innerHTML разбирает строку как разметку. Если в ней есть тег с обработчиком, чужой код выполнится на вашей странице (XSS). textContent показывает текст как текст.
Что возвращает querySelectorAll и что с этим делать?
Ответ
NodeList: список, который можно обойти через forEach или for...of. Чтобы применить map и filter, его сначала превращают в массив: Array.from(list).
Источники
- Введение в DOM (MDN) CC BY-SA, по-русски
- Document.querySelector (MDN) CC BY-SA, по-русски
- DOM Standard (WHATWG) CC BY, по-английски
- Документ: учебник learn.javascript.ru только ссылка, по-русски
- OWASP: DOM based XSS Prevention Cheat Sheet CC BY-SA, по-английски
Дальше
Теперь вы знаете: DOM: дерево страницы из JavaScript.