DOM-дерево и навигация

DOM-дерево и навигация #

DOM-дерево — иерархическое представление HTML-документа, где каждый тег, текст и комментарий становится узлом (node). JavaScript может перемещаться по связям «родитель — потомок — сосед» и так находить нужные части интерфейса без повторного поиска по всей странице.


Типы узлов #

Основные типы, с которыми работает UI-код:

ТипКонстантаПример
ЭлементNode.ELEMENT_NODE (1)<section class="widget">
ТекстNode.TEXT_NODE (3)"3 трека в очереди"
КомментарийNode.COMMENT_NODE (8)<!-- skeleton -->
ДокументNode.DOCUMENT_NODE (9)#document
const trackTitle = document.querySelector('.track-title')
trackTitle.nodeType // 1
trackTitle.nodeName // "H3"
trackTitle.textContent // текст внутри, включая вложенные элементы

Родители и предки #

Подняться от элемента к контейнеру:

const removeBtn = document.querySelector('[data-action="remove-track"]')

removeBtn.parentElement // <li class="track-row">
removeBtn.closest('.playlist-panel') // ближайший предок с классом

closest удобен в списках плейлиста: кнопка знает только себя, а обработчик находит строку целиком:

function getTrackRow(button) {
  return button.closest('[data-track-id]')
}

parentNode и parentElement почти эквивалентны для элементов; у узла документа родителя нет.


Потомки: childNodes и children #

childNodes — все дочерние узлы, включая текстовые (пробелы между тегами тоже узлы):

const cartList = document.querySelector('.cart-items')
cartList.childNodes.length // может быть больше числа <li>

children — только дочерние элементы (HTMLCollection):

const widgets = document.querySelector('.dashboard-grid').children
widgets[0] // первый виджет-колонка

Первый и последний элемент-потомок:

const grid = document.querySelector('.dashboard-grid')
grid.firstElementChild
grid.lastElementChild

Соседние узлы #

Горизонтальная навигация по разметке:

const activeWidget = document.querySelector('.widget.is-active')

activeWidget.nextElementSibling // следующий виджет в сетке
activeWidget.previousElementSibling

nextSibling / previousSibling учитывают текстовые узлы; для вёрстки надёжнее варианты с Element.


Обход дерева на практике #

Разметка виджета KPI в JSFlow:

<section class="widget" data-widget="kpi">
  <header class="widget__head">
    <h2>Конверсия</h2>
    <span class="widget__badge">live</span>
  </header>
  <p class="widget__value">4.2%</p>
</section>

Навигация без повторного querySelector:

const widget = document.querySelector('[data-widget="kpi"]')
const badge = widget.querySelector('.widget__badge')
const valueEl = widget.lastElementChild

badge.textContent // "live"
valueEl.textContent // "4.2%"

Если структура стабильна, querySelector внутри контейнера читается проще, чем цепочка children[0].children[1].


NodeList и HTMLCollection #

Результаты обхода часто «живые»: при изменении DOM коллекция обновляется автоматически.

const rows = document.querySelector('.checkout-summary').children
console.log(rows.length)

// добавили строку — length изменится без повторного запроса

querySelectorAll возвращает статический NodeList — снимок на момент вызова. При динамическом списке товаров перезапрашивайте или используйте делегирование событий.


Итог #

  • DOM — дерево узлов: элементы, текст, комментарии.
  • Вверх: parentElement, closest; вниз: children, firstElementChild.
  • Вбок: nextElementSibling, previousElementSibling.
  • children — только элементы; childNodes — все узлы, включая пробелы.
  • Для списков и форм комбинируйте локальный querySelector внутри найденного контейнера.

См. также #

Часто спрашивают

querySelector или getElementById?
getElementById быстрее и проще для одного id; querySelector гибче за счёт CSS-селекторов. Подробнее