Хотите заказать веб-сайт? Связаться с нами

DOM в JS: подробное руководство

Document Object Model (объектная модель документа) представляет собой программный интерфейс, который позволяет разработчикам взаимодействовать с веб-страницами. Это фундаментальная концепция, без которой невозможно представить современную интерактивную веб-разработку.

Когда браузер загружает страницу, он создает структурированное представление HTML-кода в виде дерева объектов, где каждый элемент, атрибут или текстовый блок становится узлом этого дерева. Именно благодаря этой модели мы можем динамически изменять содержимое страницы, реагировать на действия пользователя и создавать сложные пользовательские интерфейсы. JavaScript предоставляет разработчику полный доступ к DOM, позволяя читать, создавать, изменять и удалять элементы, а также управлять их стилями и поведением.

DOM в JS: подробное руководство

Кратко про HTML

Прежде чем углубляться в тонкости работы с DOM в JS, необходимо освежить в памяти базовые принципы построения веб-страниц.

HTML (HyperText Markup Language) — это не просто набор тегов, это скелет любого документа в интернете. Браузер интерпретирует HTML не как строку, а как иерархическую структуру. Каждый тег (например, <div>, <p>, <h1>) является контейнером, который может содержать другие теги или текст. Эта вложенность и формирует ту самую древовидную структуру, с которой впоследствии работает — DOM.

Рассмотрим простейший пример разметки. Допустим, у нас есть карточка профиля пользователя:

Пример 1

<!DOCTYPE html>
<html lang="ru">
<head>
    <title>Профиль пользователя</title>
</head>
<body>
    <section class="profileContainer" data-user-id="4587">
        <h2 id="userNameHeading">Анна Сидорова</h2>
        <p class="userBioText">Веб-дизайнер с пятилетним стажем.</p>
        <button class="actionButton">Подробнее</button>
    </section>
</body>
</html>

В этом примере важно понимать иерархию. Элемент <section> является родительским для <h2>, <p> и <button>. В свою очередь, эти три элемента являются дочерними по отношению к секции и соседними по отношению друг к другу. Атрибуты вроде class, id или data-user-id — это тоже часть структуры, они хранят метаинформацию об элементах. Понимание этой структуры критически важно, так как все методы JavaScript для навигации по DOM построены именно на поиске по этим связям: «найди родителя», «найди следующий элемент», «найди всех детей».

BOM - объектная модель браузера

Говоря о взаимодействии с браузером, нельзя обойти стороной BOM (Browser Object Model - объектная модель браузера). Если DOM отвечает за содержимое страницы, то BOM предоставляет доступ к возможностям самого браузера, которые не относятся напрямую к документу.

Это своего рода «оболочка» вокруг документа. BOM включает в себя глобальный объект window, который является корневым для всех остальных объектов, а также объекты navigator, screen, location, history и frames.

Основное отличие BOM от DOM заключается в том, что BOM не имеет официального стандарта (хотя современные браузеры реализуют его схожим образом) и управляет именно характеристиками окна браузера. Рассмотрим небольшой пример, демонстрирующий использование BOM для получения информации об устройстве пользователя и управления историей переходов:

Пример 2

// Объект navigator содержит информацию о браузере и системе
let browserLanguage = window.navigator.language;
console.log('Язык интерфейса браузера:', browserLanguage);

let userSystemInfo = window.navigator.userAgent;
console.log('Информация об устройстве:', userSystemInfo);

// Объект screen предоставляет данные о физическом экране
let screenWidthValue = window.screen.width;
let screenHeightValue = window.screen.height;
console.log('Разрешение экрана:', screenWidthValue, 'x', screenHeightValue);

// Объект history позволяет управлять историей навигации
function goToPreviousPage() {
    // Метод back имитирует нажатие кнопки "Назад" в браузере
    window.history.back();
}

// Объект location содержит URL текущей страницы и методы для навигации
function reloadPageWithDelay() {
    console.log('Перезагрузка через 2 секунды...');
    // Метод reload обновляет страницу
    setTimeout(() => window.location.reload(), 2000);
}

BOM позволяет решать такие задачи, как определение геолокации пользователя (через navigator.geolocation), работа с cookie (через document.cookie, что находится на стыке DOM и BOM), открытие новых окон (через window.open), а также управление размерами и позицией окна. Понимание BOM важно при создании адаптивных и кросс-браузерных решений, когда необходимо подстраивать интерфейс под характеристики устройства или браузера.

DOM - объектная модель документа

Перейдем к самому объемному и значимому разделу — DOM. Это основная область, где JavaScript-разработчик проводит большую часть времени, создавая интерактивность.

DOM предоставляет десятки методов и свойств для навигации по дереву документа, изменения его узлов и обработки событий. В современной разработке редко используют прямой доступ к каждому элементу через громоздкие циклы, однако понимание основ работы DOM необходимо для написания эффективного кода, даже при использовании фреймворков вроде (откроется в новой вкладке)React или (откроется в новой вкладке)Vue, которые создают свою виртуальную прослойку.

Для начала нужно научиться получать доступ к элементам. Без этого невозможны никакие дальнейшие манипуляции. Рассмотрим несколько методов поиска, используя нашу ранее созданную HTML-структуру профиля.

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

Пример 3

// 1. Поиск по уникальному идентификатору (самый быстрый способ)
let userNameElement = document.getElementById('userNameHeading');
console.log('Найден по id:', userNameElement.textContent);

// 2. Поиск по имени класса (возвращает HTMLCollection)
let profileDescriptionElements = document.getElementsByClassName('userBioText');
if (profileDescriptionElements.length > 0) {
    console.log('Текст биографии:', profileDescriptionElements[0].innerHTML);
}

// 3. Поиск по имени тега (возвращает HTMLCollection)
let allButtonElements = document.getElementsByTagName('button');
console.log('Количество кнопок на странице:', allButtonElements.length);

// 4. Современные универсальные методы (querySelector)
// querySelector возвращает ПЕРВЫЙ подходящий элемент (использует CSS-селекторы)
let firstButton = document.querySelector('.profileContainer .actionButton');
console.log('Текст на первой кнопке:', firstButton.innerText);

// 5. querySelectorAll возвращает статический NodeList со всеми подходящими элементами
let allParagraphsInsideContainer = document.querySelectorAll('.profileContainer p');
allParagraphsInsideContainer.forEach((paragraphItem, index) => {
    console.log(`Абзац ${index + 1}:`, paragraphItem.textContent);
});

Навигация по DOM-дереву

После того как элемент найден, часто требуется переместиться к соседним узлам, родителю или дочерним элементам.

Пример 4

// Получаем элемент секции
let mainContainer = document.querySelector('.profileContainer');

// Навигация по дочерним элементам
let firstChildElement = mainContainer.firstElementChild; // <h2>
let lastChildElement = mainContainer.lastElementChild;   // <button>
let allChildrenNodes = mainContainer.children;           // HTMLCollection дочерних элементов

console.log('Первый ребенок:', firstChildElement.tagName);
console.log('Последний ребенок:', lastChildElement.tagName);

// Навигация по соседним элементам
let headingElement = document.getElementById('userNameHeading');
let nextSiblingElement = headingElement.nextElementSibling; // <p> (биография)
let previousSiblingElement = headingElement.previousElementSibling; // null, так как перед h2 ничего нет

console.log('Следующий элемент после заголовка:', nextSiblingElement.className);

// Навигация к родителю
let bioTextElement = document.querySelector('.userBioText');
let parentOfBio = bioTextElement.parentElement; // <section>
console.log('Родитель биографии:', parentOfBio.tagName);

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

Самый частый сценарий — динамическое обновление текста или HTML-структуры.

Пример 5

// Находим элемент для изменения
let profileNameElement = document.getElementById('userNameHeading');

// Изменение текста (безопасно, не интерпретирует HTML)
profileNameElement.textContent = 'Иван Петров (обновлено)';

// Изменение HTML-содержимого (опасно при работе с пользовательским вводом)
let bioElement = document.querySelector('.userBioText');
bioElement.innerHTML = 'Теперь Full-stack разработчик.';

// Работа с атрибутами
let containerElement = document.querySelector('.profileContainer');

// Получение значения атрибута
let userIdValue = containerElement.getAttribute('data-user-id');
console.log('ID пользователя из data-атрибута:', userIdValue);

// Установка нового атрибута
containerElement.setAttribute('data-user-status', 'active');

// Удаление атрибута
containerElement.removeAttribute('data-user-id');

// Работа со стилями (inline-стили, обычно лучше использовать классы)
let actionBtn = document.querySelector('.actionButton');
actionBtn.style.backgroundColor = '#4CAF50';
actionBtn.style.color = 'white';
actionBtn.style.border = 'none';
actionBtn.style.padding = '10px 20px';

Работа с классами

Пример 6

let btnElement = document.querySelector('.actionButton');

// Добавление класса
btnElement.classList.add('primaryButtonTheme');

// Удаление класса
btnElement.classList.remove('actionButton');

// Переключение класса (если есть - уберет, если нет - добавит)
btnElement.classList.toggle('highlightedButton');

// Проверка наличия класса
let hasThemeClass = btnElement.classList.contains('primaryButtonTheme');
console.log('Есть ли тема у кнопки?', hasThemeClass);

Создание и удаление элементов

Динамическое создание узлов — основа для построения интерфейсов, подгружающих данные с сервера.

Пример 7

// Создание нового элемента
let newBadgeElement = document.createElement('span');

// Наполнение элемента контентом и настройка
newBadgeElement.textContent = 'Проверенный пользователь';
newBadgeElement.classList.add('verificationBadge');
newBadgeElement.style.marginLeft = '10px';
newBadgeElement.style.fontSize = '12px';
newBadgeElement.style.color = 'green';

// Вставка элемента в DOM
let nameHeading = document.getElementById('userNameHeading');
nameHeading.appendChild(newBadgeElement); // Вставит в конец заголовка

// Создаем элемент для вставки в другое место
let statusMessageElement = document.createElement('div');
statusMessageElement.id = 'temporaryMessage';
statusMessageElement.textContent = 'Профиль был обновлен минуту назад';
statusMessageElement.style.padding = '8px';
statusMessageElement.style.backgroundColor = '#e7f3fe';

// Вставляем как первый элемент в контейнер
let containerDiv = document.querySelector('.profileContainer');
containerDiv.insertBefore(statusMessageElement, containerDiv.firstChild);

// Удаление элемента
setTimeout(() => {
    let messageToRemove = document.getElementById('temporaryMessage');
    if (messageToRemove) {
        // Старый способ: messageToRemove.parentNode.removeChild(messageToRemove);
        // Современный способ:
        messageToRemove.remove();
        console.log('Временное сообщение удалено');
    }
}, 5000);

Обработка событий

DOM был бы бесполезен без возможности реагировать на действия пользователя: клики, ввод текста, движение мыши.

Пример 8

// Находим кнопку
let profileButton = document.querySelector('.profileContainer button');

// Определяем функцию-обработчик
function handleButtonClick(eventDetails) {
    // eventDetails — объект события, содержит много полезной информации
    console.log('Событие инициировано элементом:', eventDetails.target);
    console.log('Тип события:', eventDetails.type);
    
    alert('Спасибо за интерес к профилю!');
    
    // Пример: отключаем кнопку после нажатия
    eventDetails.target.disabled = true;
    eventDetails.target.textContent = 'Уже нажато';
}

// Навешиваем обработчик события "click"
profileButton.addEventListener('click', handleButtonClick);

// Пример обработки ввода в текстовое поле (гипотетическое поле поиска)
// Представим, что на странице есть 
let searchInputField = document.querySelector('.searchField'); // может быть null, если нет поля

if (searchInputField) {
    searchInputField.addEventListener('input', (eventObject) => {
        let currentValue = eventObject.target.value;
        console.log('Текущий поисковый запрос:', currentValue);
        
        // Здесь могла бы быть логика фильтрации списка пользователей
        if (currentValue.length > 3) {
            console.log('Отправляем запрос на сервер для:', currentValue);
        }
    });
}

Таблица основных свойств и методов DOM

Для удобства восприятия сведем ключевые методы в таблицу.
Категория Метод / Свойство Краткое описание
Поиск getElementById() Ищет элемент по уникальному атрибуту id.
getElementsByClassName() Ищет все элементы с указанным классом.
querySelector() Ищет первый элемент, подходящий под CSS-селектор.
querySelectorAll() Ищет все элементы, подходящие под CSS-селектор.
Навигация parentElement Возвращает родительский элемент узла.
children Возвращает коллекцию дочерних элементов (только теги).
nextElementSibling Возвращает следующий соседний элемент.
previousElementSibling Возвращает предыдущий соседний элемент.
Изменение textContent Получает или задает текстовое содержимое элемента.
innerHTML Получает или задает HTML-содержимое (разбирает теги).
setAttribute() Устанавливает значение атрибута.
classList.add() / remove() Добавляет или удаляет классы у элемента.
Манипуляции createElement() Создает новый элемент (пока не в DOM).
appendChild() Добавляет элемент в конец другого элемента.
remove() Удаляет элемент из DOM.
События addEventListener() Назначает обработчик события на элемент.
Из ноутбука появляются летающие светящиеся экраны

Оживи свой сайт. Освой JavaScript!

Статичная верстка — это только скелет. Наш онлайн-курс "JavaScript с нуля до профи" даст твоим страницам мышцы и нервы. Научись создавать слайдеры, формы, интерактивные карты и получать данные с сервера.

От теории — к реальным скриптам в твоём портфолио.

Подробнее о курсе

Часто задаваемые вопросы о DOM

Чем отличается textContent от innerHTML при работе с DOM?

Главное различие кроется в способе интерпретации переданного значения. Свойство textContent воспринимает любой переданный текст как простую строку и экранирует все html-теги, превращая их в обычный текст. Если вы попытаетесь вставить через него строку <div>test</div>, пользователь увидит на экране именно эти символы, а не новый элемент. Свойство innerHTML, напротив, парсит переданную строку и создает настоящие DOM-узлы на основе тегов, что удобно для вставки сложной разметки, но требует осторожности из-за риска внедрения скриптов.

Почему после создания элемента через createElement его не видно на странице?

Метод document.createElement('div') лишь создает объект в памяти браузера, но не добавляет его автоматически в видимую структуру документа. Это похоже на изготовление кирпича, который пока лежит на земле и не стал частью здания. Чтобы элемент отобразился, необходимо явно указать место его вставки с помощью методов вроде parentElement.appendChild(newElement) или parentElement.insertBefore(newElement, referenceElement). Только после выполнения одного из этих методов элемент становится частью DOM и появляется на веб-странице.

Что такое всплытие событий и как оно влияет на обработку кликов?

Всплытие (bubbling) — это механизм DOM, при котором событие, возникшее на самом глубоком вложенном элементе, последовательно передается его родителям, затем родителям родителей и так до самого корня html. Например, при клике на кнопку внутри <div> событие сначала сработает на кнопке, потом на этом диве, а затем на <body>. Это позволяет назначать один обработчик на контейнер и ловить события от всех его дочерних элементов, что часто используется для оптимизации производительности и работы с динамически добавляемыми блоками.

Как правильно удалить обработчик события, добавленный через addEventListener?

Для удаления обработчика существует метод removeEventListener, но он сработает корректно только при передаче точно такой же функции, которая была назначена изначально. Проблема часто возникает, когда обработчик объявляется как анонимная функция прямо внутри addEventListener — в этом случае ссылка на нее теряется и удалить ее становится невозможно. Правильным подходом является создание именованной функции отдельно и передача ее имени в оба метода: element.addEventListener('click', myNamedHandler) и позже element.removeEventListener('click', myNamedHandler).

В чем разница между свойствами children и childNodes?

Свойство children возвращает коллекцию, содержащую только дочерние узлы-элементы, то есть html-теги. Оно игнорирует текстовые узлы, переносы строк и комментарии. Свойство childNodes более «честное» — оно возвращает все дочерние узлы любого типа, включая текстовые узлы с пробелами. Если в вашем коде между тегами есть отступы, childNodes покажет их как отдельные текстовые элементы, что часто приводит к неожиданным результатам при переборе, если вы ожидаете получить только теги.

Можно ли с помощью DOM изменить псевдоэлементы вроде ::before или ::after?

Напрямую обратиться к псевдоэлементам через DOM нельзя, так как они не являются частью дерева узлов. Однако управлять их содержимым и стилями можно косвенно через добавление и удаление классов у основного элемента или через изменение атрибута style с помощью вставки специальных CSS-правил. Например, можно динамически создать <style> блок и добавить его в <head>, прописав там правило для псевдоэлемента с нужным контентом или оформлением, что даст аналогичный эффект.

Какой метод поиска элементов в DOM считается самым производительным?

Самым быстрым методом традиционно считается getElementById(), потому что браузер хранит индекс всех элементов с идентификаторами и обращается к ним напрямую, без обхода всего дерева. Методы getElementsByClassName и getElementsByTagName также довольно производительны и возвращают живые коллекции. Универсальные querySelector и querySelectorAll немного медленнее, так как включают парсинг CSS-селектора, но их гибкость и удобство часто перевешивают незначительную потерю в скорости для большинства практических задач.

Заключение

В заключение стоит отметить, что глубокое понимание DOM JS открывает перед разработчиком безграничные возможности по созданию интерактивных веб-интерфейсов. Это та область, где теория HTML превращается в живую страницу.

Освоив работу с DOM и научившись управлять поведением страницы, вы делаете важный шаг от простого верстальщика к полноценному фронтенд-разработчику. Но теория, даже самая подробная, дает лишь фундамент — настоящее мастерство приходит с практикой. Если вы хотите систематизировать знания и уверенно писать код для сложных интерактивных проектов, обратите внимание на наш онлайн-курс «Обучение JavaScript с нуля до профи». Мы разбираем реальные кейсы, закрываем пробелы в понимании и помогаем выйти на новый уровень. А если вам нужен не обученный специалист, а готовый работающий сайт под ключ, — вы всегда можете заказать создание сайта у нашей веб-студии: мы воплотим любую идею в качественный и современный продукт.

Теги: