Объектная модель документа (DOM): что это и как она устроена
Разбираемся, как JavaScript взаимодействует с HTML.


Иллюстрация Polina Vari для Skillbox Media
DOM — это механизм, благодаря которому браузер понимает и отображает HTML‑страницу.
Когда браузер считывает код, он превращает его в структуру из взаимосвязанных объектов: заголовков, абзацев, изображений и других элементов. Эти объекты можно изменять с помощью JavaScript: добавлять новые, удалять лишние, менять текст или оформление.
DOM нужен для того, чтобы JavaScript мог взаимодействовать со страницей уже после её загрузки. Без него браузер просто показал бы набор тегов и текста, а страница оставалась бы статичной. Благодаря DOM код может изменить текст кнопки, скрыть блок, добавить абзац или отреагировать на нажатие.
Именно DOM делает веб-страницы интерактивными — они могут меняться прямо в браузере, без повторной загрузки.
Содержание
- Как устроен DOM
- Как HTML-документ превращается в DOM-дерево
- Из каких типов узлов состоит DOM
- Как получить доступ к элементам
- Свойства DOM-элементов
- Как изменить содержимое и структуру страницы
- Как создать, клонировать и удалить DOM-элементы с помощью JS
- Что такое атрибуты и как с ними работать
- Чем DOM отличается от визуального представления
- Полезные советы
Как устроен DOM
Дерево — это способ хранения данных, при котором элементы выстраиваются в иерархию: один является «родителем», а другие — его «потомками».
У каждого узла (элемента) может быть несколько подузлов, но только один родитель. На самом верху находится корень — главный элемент, с которого всё начинается. У корня нет родителя, а узлы без потомков называются листьями.
Классический пример такой структуры — папки на компьютере. Корнем может быть диск C, внутри него — папка «Пользователи», а в ней — отдельные каталоги для каждого пользователя. Чтобы добраться до файла, нужно пройти по веткам дерева от корня к нужному листу.
DOM (Document Object Model, объектная модель документа) — это то же самое дерево, только вместо папок и файлов в нём находятся элементы веб-страницы.
Когда вы открываете сайт, браузер получает HTML-код и превращает его в иерархию объектов. Каждый тег — это отдельный узел этого дерева: тег <html> — корень, внутри него теги <head> и <body>, внутри <body> — параграфы, ссылки, изображения и так далее.
<!DOCTYPE html>
<html>
<head>
<title>Пример</title>
</head>
<body>
<h1>Заголовок</h1>
<p>Это параграф.</p>
</body>
</html>
С помощью DOM можно добавлять или удалять элементы, менять текст и стили, реагировать на действия пользователя. Когда вы нажимаете кнопку и на странице появляется всплывающее окно — это работа с DOM: скрипт изменяет дерево, и браузер сразу обновляет то, что вы видите.
Как HTML-документ превращается в DOM-дерево
Браузер читает HTML сверху вниз. Он видит первый тег <html> и создаёт корневой элемент дерева — то, от чего потом отрастают ветви. Затем встречает <head> и <body> — добавляет их как дочерние узлы. Так постепенно из каждого тега появляется объект: у <p> — узел параграфа, у <img> — узел изображения, у <a> — ссылка. Всё, что находится между тегами, превращается в текстовые узлы.
Когда браузер доходит до конца документа, дерево готово. Это и есть DOM — внутренняя модель страницы. Теперь JavaScript может преображать её как угодно: добавлять элементы, менять стили, удалять текст. А браузер мгновенно обновляет интерфейс, потому что следит за деревом и перерисовывает то, что изменилось.
Вот короткий пример HTML и его DOM-дерева:
<!DOCTYPE html>
<html>
<body>
<h1>Привет, мир!</h1>
<p>Это абзац текста.</p>
</body>
</html>
DOM-дерево для этого кода выглядит так:
Document
├─ DocumentType: html
└─ Element: html
└─ Element: body
├─ Element: h1
│ └─ Text: "Привет, мир!"
└─ Element: p
└─ Text: "Это абзац текста."
Здесь:
- Document — корень документа.
- DocumentType — объявление <!DOCTYPE html>.
- Element — HTML-элементы: <html>, <body>, <h1>, <p>.
- Text — текстовое содержимое тегов.
Из каких типов узлов состоит DOM
DOM-дерево устроено так, что каждая часть HTML превращается в узел (node) определённого типа. Таких типов немного, но вместе они описывают всё, что есть в документе.
Узел документа (Document) — это корневой объект всего DOM-дерева, отправная точка, с которой начинается работа со страницей.
Когда браузер загружает HTML, он создаёт объект document, в котором хранится всё содержимое страницы: теги, текст, скрипты, стили и даже комментарии. Можно сказать, что document — это представление всей веб-страницы в памяти браузера.
Например, если в HTML написано:
<!DOCTYPE html>
<html>
<head>
<title>Пример</title>
</head>
<body>
<p>Привет, мир!</p>
</body>
</html>
То в JavaScript вы можете обратиться к разным частям этого документа так:
document.title; // "Пример"
document.body; // <body>...</body>
document.documentElement; // <html>...</html>
Объект document — это и узел, и глобальная точка доступа к DOM. Через него создают, находят и изменяют элементы:
const p = document.createElement('p'); // Создаём новый элемент
p.textContent = 'Новый текст';
document.body.appendChild(p); // Добавляем его на страницу
Узел элемента (element node) — любой HTML-тег на странице.Элементы могут содержать другие элементы и текст.
Каждый раз, когда браузер встречает тег вроде <div>, <p>, <img> или <button>, он создаёт узел элемента. Этот узел хранит всю информацию о теге: его имя, атрибуты, дочерние элементы и текст внутри.
Например, возьмём фрагмент HTML:
<div id="container">
<p class="text">Привет, мир!</p>
</div>
- Элемент div — это корневой узел внутри этого фрагмента.
- У него есть дочерний элемент p — тоже узел элемента
- Внутри <p> есть текстовый узел, который хранит строку Привет, мир!.
Доступ к узлу элемента можно получить через JavaScript:
// Находим элемент <p> — это узел элемента
const paragraph = document.querySelector('.text');
// Получаем информацию об узле
console.log(paragraph.nodeType); // 1 — тип узла «элемент»
console.log(paragraph.nodeName); // P — имя тега
console.log(paragraph.tagName); // Тоже P
Тип 1 указывает, что это именно element node, а не, например, текст (3) или комментарий (8).
Через такие узлы JavaScript управляет страницей: меняет текст (textContent), классы (classList), стили (style) и структуру (append, remove, cloneNode).
Текстовый узел (Text) — это часть DOM-дерева, в которой хранится текст. Когда браузер разбирает HTML, он превращает каждый кусочек текста между тегами в отдельный узел. У такого узла нет атрибутов, классов или дочерних элементов — только текстовое содержимое.
Например, возьмём простой HTML-код:
<p>Привет, мир!</p>
В DOM это превращается в структуру:
- узел элемента <p>
└── текстовый узел "Привет, мир!"
Проверим в JavaScript:
const p = document.querySelector('p');
console.log(p.firstChild.nodeType); // 3 — это текстовый узел
console.log(p.firstChild.nodeValue); // "Привет, мир!"
Здесь:
nodeType === 3 означает, что перед нами текстовый узел.
Можно создать такой узел вручную:
const text = document.createTextNode('Новый текст');
document.body.appendChild(text);
Или просто задать текст элементу:
const p = document.createElement('p');
p.textContent = 'Новый абзац';
document.body.appendChild(p);
Оба способа создают текстовый узел внутри тега.
Атрибут (Attribute) — это часть DOM, которая описывает дополнительные свойства HTML-элемента: класс, идентификатор, ссылку, источник изображения и так далее. В DOM атрибуты тоже представлены как узлы, хотя напрямую в дереве они обычно не отображаются — они принадлежат элементу.
Когда браузер разбирает HTML, он создаёт для каждого атрибута отдельный узел, связанный с конкретным элементом. Например:
<img src="cat.jpg" alt="Кот">
В DOM-дереве у тега <img> есть два узла атрибута:
- src со значением "cat.jpg";
- alt со значением "Кот".
Каждый такой узел содержит пару «имя — значение». Если посмотреть через JavaScript:
const img = document.querySelector('img');
console.log(img.getAttribute('src')); // "cat.jpg"
console.log(img.attributes[0].name); // "src"
console.log(img.attributes[0].value); // "cat.jpg"
Раньше атрибуты считались отдельным типом узлов (nodeType === 2), но современные браузеры хранят их как свойства элемента. С ними работают через методы:
img.setAttribute('alt', 'Милый кот'); // Изменить значение
img.getAttribute('alt'); // Получить значение
img.hasAttribute('alt'); // Проверить, есть ли атрибут
img.removeAttribute('alt'); // Удалить атрибут
Комментарий (Comment). Даже комментарии из HTML (<!-- комментарий -->) становятся отдельными узлами, хотя на странице их не видно. Это нужно, чтобы JavaScript мог при желании их читать, изменять или удалять.
Например:
<div>Контент</div>
<!-- Это комментарий -->
В DOM это выглядит так:
- элемент <div>;
- узел-комментарий с текстом "Это комментарий".
Проверим это в коде:
const comment = document.body.childNodes[1]; // Второй узел — комментарий
console.log(comment.nodeType); // 8 — это тип узла "Comment"
console.log(comment.nodeValue); // "Это комментарий"
Можно создать комментарий вручную:
const newComment = document.createComment('Создан через JS');
document.body.appendChild(newComment);
Как получить доступ к элементам
У JavaScript несколько методов доступа к элементам DOM:
getElementById(). Ищет элемент по уникальному идентификатору. Например, если в коде есть <div id="header">, то в JavaScript его можно получить так:
const header = document.getElementById('header');
После этого header становится объектом, с которым можно работать: менять его содержимое, стили, атрибуты.
getElementsByClassName(). Используют, если есть несколько элементов с одинаковым классом. Метод возвращает коллекцию — не один элемент, а список всех подходящих. Например:
const buttons = document.getElementsByClassName('btn');
Теперь buttons содержит все элементы с классом btn (кнопки).
querySelector() и querySelectorAll(). Более современные и гибкие способы, которые работают как CSS-селекторы. Можно искать элементы по тегу, классу, id или по сложному условию:
const link = document.querySelector('nav a.active'); // Первая активная ссылка в меню
const allLinks = document.querySelectorAll('nav a'); // Все ссылки в навигации
querySelector возвращает первый найденный элемент, querySelectorAll — список всех.
Есть и другие методы: getElementsByTagName() — ищет все элементы с определённым тегом (<p>, <div>), а document.body и document.documentElement позволяют напрямую обратиться к основным частям страницы — телу документа и корневому тегу <html>.
Свойства DOM-элементов
DOM-элементы имеют множество свойств, которые позволяют управлять их содержимым и стилем:
innerHTML — это свойство, которое возвращает или задаёт вложенный HTML-код элемента как строку. Проще говоря, когда вы читаете elem.innerHTML, вы получаете текст с разметкой, внутри тега, а когда присваиваете elem.innerHTML = '…' — браузер парсит строку как HTML и заменяет всё содержимое элемента на новое.
const content = document.querySelector('div').innerHTML;
Если присвоить новое значение, браузер перерисует содержимое элемента:
div.innerHTML = '<p>Новый текст</p>';
Самая серьёзная опасность — XSS: если вставлять через innerHTML данные, полученные от пользователя или из ненадёжного источника, в строке может оказаться вредоносный код.
textContent: получает или устанавливает текстовое содержимое элемента без HTML-тегов.
const text = document.querySelector('p').textContent;
classList: позволяет управлять классами элемента (добавлять, удалять, проверять наличие).
const element = document.querySelector('.my-element');
element.classList.add('active');
style: Позволяет изменять стили элемента через JavaScript.
element.style.color = 'red';
Кроме этих свойств, у элементов есть десятки других: id, value, href, src, dataset и так далее — в зависимости от типа тега. Можно не только найти элемент, но и управлять им как полноценным объектом программы.
Как изменить содержимое и структуру страницы
JavaScript может менять DOM-дерево: добавлять новые ветви, удалять старые или перестраивать узлы. Для этого есть набор методов:
appendChild(). Добавляет новый узел как последний дочерний элемент в конец родительского элемента.
const newDiv = document.createElement('div');
document.body.appendChild(newDiv);
prepend(). Добавляет дочерний элемент в начало родительского элемента.
const p2 = document.createElement('p');
p2.textContent = 'Привет в начале!';
document.body.prepend(p2);
insertBefore(). Вставляет элемент перед указанным элементом.
const p3 = document.createElement('p');
p3.textContent = 'Вставлено перед первым абзацем';
document.body.insertBefore(p3, p);
removeChild (node). Удаляет указанный дочерний узел.
const divToRemove = document.querySelector('div');
document.body.removeChild(divToRemove);
replaceChild (newNode, oldNode). Заменяет один дочерний узел другим.
const newParagraph = document.createElement('p');
const oldParagraph = document.querySelector('p');
document.body.replaceChild(newParagraph, oldParagraph);
Как создавать, клонировать и удалять DOM-элементы с помощью JS
Чтобы создать элемент, используют метод:
document.createElement('тег');
Например:
const newDiv = document.createElement('div');
newDiv.textContent = 'Привет!';
document.body.appendChild(newDiv);
Иногда нужно не создавать, а скопировать уже существующий элемент. Для этого есть метод:
element.cloneNode(true);
Если передать true, то браузер клонирует узел вместе со всем содержимым: текстом, потомками и атрибутами. Без аргумента (false) копируется только сам элемент, без вложенных тегов. Например:
const cardCopy = card.cloneNode(true);
document.body.appendChild(cardCopy);
Удаление проводится просто — с помощью метода remove():
newDiv.remove();
Он сразу исключает узел из DOM-дерева.
Благодаря этим методам JavaScript может собирать страницу на ходу: добавлять карточки товаров, клонировать шаблоны, убирать лишние блоки. Всё это происходит мгновенно, без перезагрузки, потому что браузер просто меняет части DOM-дерева прямо в памяти.
Что такое атрибуты и как с ними работать
Атрибуты — это дополнительные данные внутри HTML-тегов, которые задают поведение или внешний вид элемента. Например, в ссылке <a href="https://skillbox.ru"> атрибут href указывает адрес, куда ведёт ссылка. Когда страница превращается в DOM, каждый атрибут становится частью объекта элемента. JavaScript может их читать, изменять или удалять прямо во время работы страницы. Для этого существует три базовых метода.
getAttribute() возвращает значение атрибута.
const link = document.querySelector('a');
console.log(link.getAttribute('href'));
Этот код выведет адрес, указанный в теге.
setAttribute() создаёт новый атрибут или меняет существующий:
link.setAttribute('href', 'https://skillbox.ru/media/');
Теперь ссылка ведёт на другой сайт. Если такого атрибута не было, он появится автоматически.
hasAttribute() проверяет, есть ли у элемента нужный атрибут.
if (link.hasAttribute('target')) {
console.log('Ссылка открывается в новой вкладке');
}
Есть и противоположный метод — removeAttribute(), который удаляет атрибут совсем:
link.removeAttribute('title');
Чем DOM отличается от визуального представления
DOM — это структура данных, а не картинка. Но само по себе DOM-дерево ничего не рисует.
Чтобы превратить эту структуру в видимую страницу, браузер проходит ещё несколько этапов. После создания DOM он разбирает CSS и строит CSSOM — дерево стилей. Затем объединяет оба дерева и формирует render tree — модель, которая описывает, какие элементы должны попасть на экран и как именно они должны выглядеть.
Дальше начинается layout — расчёт размеров, координат и расположения каждого блока. Браузер определяет, сколько пикселей займёт текст, где будут границы, как элементы будут размещены друг относительно друга.
И наконец — рендеринг (painting). На этом этапе браузер действительно рисует на экране всё: текст, цвета, изображения, тени.
DOM участвует в этом процессе как основа, но не совпадает с тем, что вы видите. Например, если элемент скрыт через display: none, он остаётся в DOM, но в визуальном представлении его просто нет. Или наоборот: при CSS-анимации DOM не меняется, хотя на экране происходят движения.
Полезные советы
Сокращайте количество обращений к DOM
Каждый поиск элемента заставляет браузер обходить всё дерево. Если элемент нужен не один раз, сохраните его в переменную.
// Нашли один раз и используем
const button = document.querySelector('.button');
button.textContent = 'OK';
button.classList.add('active');
Объединяйте изменения в памяти
Каждое добавление элемента вызывает перерисовку страницы. Если вставляете много элементов, соберите их сначала в памяти с помощью DocumentFragment, а потом вставьте за один раз.
const list = document.querySelector('ul');
const fragment = document.createDocumentFragment();
for (let i = 1; i <= 5; i++) {
const li = document.createElement('li');
li.textContent = `Элемент ${i}`;
fragment.appendChild(li);
}
list.appendChild(fragment); // Браузер перерисует только один раз
Используйте классы, а не inline-стили
Вместо того чтобы менять цвета, размеры и шрифты прямо через style, лучше добавлять или убирать классы. Так код будет чище, а за оформление станет отвечать CSS.
JS:
element.classList.add('active');
CSS:
.active {
background-color: red;
color: white;
}
Делегируйте события
Если элементов много или они создаются динамически, не навешивайте обработчик на каждый. Вместо этого добавьте один обработчик на общий контейнер.
// Вместо множества обработчиков на каждую кнопку:
document.querySelectorAll('.btn').forEach(btn => {
btn.addEventListener('click', () => console.log('Нажато'));
});
// Один обработчик на родителе:
document.querySelector('.buttons').addEventListener('click', e => {
if (e.target.matches('.btn')) {
console.log('Нажато:', e.target.textContent);
}
});
Пишите читаемый код
Лучше потратить лишние 10 строк, но сделать код понятнее. Длинные функции разбивайте на небольшие, давайте переменным осмысленные имена.
function createListItem(text) {
const li = document.createElement('li');
li.textContent = text;
return li;
}
const list = document.querySelector('ul');
list.appendChild(createListItem('Новый пункт'));
Эти принципы просты, но именно они делают код живых интерфейсов лёгким, быстрым и понятным. DOM-операции остаются под контролем, страница не тормозит, а разработка превращается не в борьбу с браузером, а в аккуратную настройку его поведения.
Больше интересного про код — в нашем телеграм-канале. Подписывайтесь!