Гид по технологиям

Полное руководство по обходу DOM в JavaScript

7 min read Web‑разработка Обновлено 10 Apr 2026
Обход DOM в JavaScript — полное руководство
Обход DOM в JavaScript — полное руководство

Большой логотип JavaScript на синем фоне

Что такое обход DOM

DOM (Document Object Model) — это представление HTML-документа в виде дерева узлов. Каждый HTML-элемент, текстовая часть и комментарий — это узел. DOM предоставляет API для доступа и изменения структуры, содержимого и стилей страницы с помощью JavaScript.

Определение термина: узел — элемент дерева DOM (элемент, текст, комментарий и т.д.).

Обход DOM (DOM traversal) — выборка узлов относительно другого узла: движения вниз к детям, вверх к родителям и в стороны к соседям. Часто эффективнее начать поиск от уже известного узла, чем сканировать весь документ.

Важно: некоторые коллекции в DOM «живые» (live), а некоторые — статические. Разница влияет на поведение при изменении дерева.

Кому это нужно

  • Frontend-разработчикам: для выборки элементов и управления интерфейсом.
  • Тестировщикам: для написания селекторов в end-to-end тестах.
  • Инженерам по производительности: для оптимизации операций манипуляции DOM.

Пример документа для практики

Ниже — пример HTML, на котором будут демонстрироваться приёмы обхода. Сохраните фрагмент как файл и экспериментируйте в консоли браузера:

  
  
  
  
      
      
      
    Sample page  
  
  
  

My Page Title

Nice caption goes here

List of amazing fruits

Must eat fruits

  • Apples
  • Oranges
  • Avocados
  • Grapes
    • Moon drops
    • Sultana
    • Concord
    • Crimson Seedless
  • Bananas

Amazing places in Kenya

Must visit places in Kenya

  • Maasai Mara
  • Diani Beach
  • Watamu Beach
  • Amboseli national park
  • Lake Nakuru

Движение вниз по дереву

Движение вниз означает переход от родителя к дочерним узлам. Наиболее распространённые способы:

  • selector methods: element.querySelector / element.querySelectorAll
  • свойства children и childNodes
  • специальные свойства firstChild и lastChild

Селекторные методы

querySelector и querySelectorAll принимают CSS-селектор и ищут элементы внутри текущего узла.

Примеры:

const firstArticle = document.querySelector('.first__article');
const allH2 = document.querySelectorAll('h2');
const firstH2 = allH2[0];
const secondH2 = allH2[1];

Особенности:

  • querySelector возвращает первый совпавший элемент или null.
  • querySelectorAll возвращает статический NodeList (не «живой»).
  • Внутри селектора используйте CSS-символы: “.class” для классов, “#id” для id, и т.д.

Чтобы получить содержимое элемента, используйте свойства innerHTML, textContent или value (для input):

document.querySelector('.orange').innerHTML

Совет по доступности: для извлечения текста предпочитайте textContent — он возвращает текст без разметки.

children vs childNodes

  • children возвращает HTMLCollection только с дочерними элементами (элементными узлами).
  • childNodes возвращает NodeList со всеми дочерними узлами, включая текстовые (например пробелы и переносы строк), комментарии.

Пример:

const appleList = document.querySelector('.apple-list');
const apples = appleList.children; // HTMLCollection
console.log(apples[0]);

Замечание: HTMLCollection — «живой», то есть при изменении DOM коллекция обновляется автоматически. NodeList от querySelectorAll — обычно статический.

firstChild и lastChild

Эти свойства возвращают первый и последний дочерний узел (node). Если вам нужны именно элементные узлы, лучше использовать firstElementChild / lastElementChild.

const appleList = document.querySelector('.apple-list');
const first = appleList.firstElementChild; // безопаснее, возвращает элемент
const last = appleList.lastElementChild;

Используйте firstChild/lastChild только если вы ожидаете текстовые узлы или комментарии.

Движение вверх по дереву

Навигация к родительским узлам нужна, когда вы находитесь внутри элемента и хотите найти ближайший контейнер.

Основные методы:

  • parentElement / parentNode
  • closest

parentElement vs parentNode

  • parentElement возвращает родительский элемент или null, если родитель — не элемент.
  • parentNode возвращает родительский узел любого типа (например, DocumentFragment).

Пример:

const appleList = document.querySelector('.apple-list');
const parentDiv = appleList.parentElement; // div.wrapper-1

closest

closest поднимается вверх по дереву и возвращает ближайший предок, соответствующий селектору. Если совпадений нет — null.

const btn1 = document.querySelector('.btn-1');
const mainEl = btn1.closest('main');

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

Движение в стороны

Чтобы перемещаться между элементами на одном уровне (соседями), используйте:

  • nextElementSibling / previousElementSibling — возвращают следующий/предыдущий элементный узел.
  • nextSibling / previousSibling — возвращают любой следующий/предыдущий узел (включая текст).

Пример:

const orange = document.querySelector('.orange');
const apple = orange.previousElementSibling;
const avocado = orange.nextElementSibling;

Используйте Element-sibling версии для предсказуемого поведения с элементными узлами.

Чейннинг свойств и методов

Комбинируя методы, можно переходить сразу в нужную ветку дерева:

const grapesType1 = document
  .querySelector('.first__article')
  .querySelector('.apple-list')
  .querySelector('.grape')
  .querySelector('.type-1');

Предупреждение: длинные цепочки без проверок могут выбрасывать ошибки, если промежуточный узел равен null. Для безопасного обхода используйте проверки или optional chaining:

const maybeType1 = document
  .querySelector('.first__article')
  ?.querySelector('.apple-list')
  ?.querySelector('.grape')
  ?.querySelector('.type-1');

if (maybeType1) {
  // безопасно работать
}

Частые ошибки и когда методы не работают

  1. Пробелы и переносы в HTML добавляют текстовые узлы, и childNodes может вернуть неожиданные элементы.
  2. Использование firstChild/lastChild без ожидания текстовых узлов приводит к отсутствию нужного элемента.
  3. querySelectorAll возвращает статический NodeList — изменения DOM после вызова не отобразятся в нём.
  4. parentElement вернёт null для корневого узла или если родитель — не элемент.
  5. Фрагменты DocumentFragment и Shadow DOM имеют свои особенности: некоторые глобальные селекторы не работают внутри shadow root.

Пример ошибки:

const list = document.querySelector('.apple-list');
const first = list.firstChild; // может быть текстовый узел (пробел), не 
  • console.log(first.nodeType); // 3 — текстовый узел
  • Решение: используйте firstElementChild или фильтруйте по nodeType === 1.

    Производительность и выбор метода

    • document.getElementById / getElementsByClassName / getElementsByTagName обычно быстрее для простых селекторов, но современные движки оптимизируют querySelectorAll.
    • Если вы повторно используете результат, кэшируйте ссылку на элемент, вместо частых вызовов селекторов.
    • Для больших документов ограничьте область поиска: вызывайте querySelector на контейнере, а не на document.

    Правило: минимизируйте количество обращений к DOM и скопируйте элементы во фрагмент (DocumentFragment) при массовой вставке.

    Шаблоны и рекомендации

    Мини-методология для безопасных манипуляций с DOM:

    1. Найдите ближний стабильный контейнер (id или уникальный класс).
    2. Ограничьте поиск этим контейнером.
    3. Используйте element.querySelector/querySelectorAll для сложных селекторов.
    4. Проверяйте результат перед доступом к свойствам (null-check).
    5. Для частых обновлений извлекайте данные, изменяйте в памяти, затем применяйте единым обновлением.

    Шаблон доступа с проверкой:

    function getTextOf(selector, root = document) {
      const el = root.querySelector(selector);
      return el ? el.textContent.trim() : null;
    }

    Шаблоны для событий

    • Делегирование событий: вешайте обработчик на контейнер и определяйте целевой элемент через event.target.closest.
    document.querySelector('.apple-list').addEventListener('click', (e) => {
      const li = e.target.closest('li');
      if (!li) return;
      // обработка клика по li
    });

    Преимущество: меньше обработчиков, лучше производительность и простота динамически созданных элементов.

    Рольовые контрольные списки

    Разработчик:

    • Кэшировать элементы при повторном использовании.
    • Использовать querySelector внутри минимального контейнера.
    • Проверять существование узлов перед доступом.
    • Предпочитать textContent для извлечения текста.

    Тестировщик QA:

    • Проверить селекторы на устойчивость при изменении разметки.
    • Убедиться, что делегирование событий работает для динамически добавляемых элементов.
    • Проверить поведение в старых браузерах, если проект поддерживает их.

    Архитектор:

    • Определить контракт DOM: какие классы/атрибуты используются как селекторы.
    • Рекомендовать уникальные идентификаторы для критичных компонентов.
    • Минимизировать использование селекторов, зависящих от структуры, если структура меняется часто.

    Критерии приёмки

    • Функция выборки возвращает корректный элемент при ожидаемой структуре.
    • Ненайденные элементы обрабатываются без ошибок (возврат null или безопасное поведение).
    • Для массовых операций изменения DOM минимизированы (атомарная вставка через DocumentFragment).
    • Селекторы устойчивы к незначительным изменениям разметки (тесты покрывают ключевые сценарии).

    Примеры анти-паттернов и альтернативы

    Анти-паттерн: полагаться на порядок детей (например, всегда брать children[2]) — хрупко при изменении верстки.

    Альтернатива: использовать семантические селекторы или data-атрибуты:

  • Apples
  • И затем:

    const primary = document.querySelector('[data-role="primary-fruit"]');

    Это делает селектор устойчивым к перестановкам и перестраиваниям DOM.

    Совместимость и миграция

    • optional chaining (?.) поддерживается в современных браузерах. Для старых окружений используйте полифиллы или явные проверки.
    • Для работы внутри Shadow DOM методы querySelector работают в пределах shadow root, но глобальные document-селектора туда не доберутся.
    • Если поддерживаете IE11, избегайте optional chaining и некоторых новых API; используйте feature detection.

    Практические приёмы и советы

    • Используйте firstElementChild / lastElementChild для предсказуемости.
    • Для поиска по тексту применяйте XPath только в редких случаях — XPath выражения сложнее и реже используются.
    • При динамической вставке создавайте шаблоны