Навигация по DOM

Навигация по DOM — перемещение между узлами DOM-дерева: к родителям, детям и соседним элементам, используя встроенные свойства узлов.

Зачем нужно

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

Где используется

  • Обход DOM в плагинах и компонентах
  • Делегирование событий (поиск родительского контейнера)
  • Построение кастомных виджетов (аккордеоны, табы)
  • Динамическая вставка элементов рядом с существующими

Предпосылки

DOM дерево, Поиск элементов

Навигация по всем узлам (включая текстовые)

const parent = document.querySelector('.container');

// Дочерние узлы
parent.childNodes;        // NodeList всех дочерних узлов (текст, комментарии, элементы)
parent.firstChild;        // первый дочерний узел
parent.lastChild;         // последний дочерний узел
parent.hasChildNodes;   // есть ли дочерние узлы (boolean)

// Родительский узел
parent.parentNode;        // родительский узел

// Соседние узлы
parent.nextSibling;       // следующий узел-сосед
parent.previousSibling;   // предыдущий узел-сосед

// Пример: текстовые узлы между элементами
// <div> <span>A</span> <span>B</span> </div>
const div = document.querySelector('div');
console.log(div.childNodes.length);    // 5 (текст, span, текст, span, текст)
console.log(div.firstChild.nodeType);  // 3 (Text — пробел/перенос)

Навигация только по элементам (без текстовых узлов)

const parent = document.querySelector('.container');

// Дочерние элементы
parent.children;             // HTMLCollection только элементов
parent.firstElementChild;    // первый дочерний элемент
parent.lastElementChild;     // последний дочерний элемент
parent.childElementCount;    // количество дочерних элементов

// Родительский элемент
parent.parentElement;        // родительский элемент

// Соседние элементы
parent.nextElementSibling;       // следующий элемент-сосед
parent.previousElementSibling;   // предыдущий элемент-сосед

// Пример: только элементы
// <div> <span>A</span> <span>B</span> </div>
const div = document.querySelector('div');
console.log(div.children.length);            // 2 (только span)
console.log(div.firstElementChild.tagName);  // "SPAN"

Сравнение: все узлы vs только элементы

Все узлы Только элементы
parentNode parentElement
childNodes children
firstChild firstElementChild
lastChild lastElementChild
previousSibling previousElementSibling
nextSibling nextElementSibling
// Разница на практике
// <ul>
//   <li>Первый</li>
//   <li>Второй</li>
// </ul>
const ul = document.querySelector('ul');

// childNodes включает текстовые узлы (переносы строк)
console.log(ul.childNodes.length);   // 5 (текст, li, текст, li, текст)

// children — только элементы
console.log(ul.children.length);     // 2 (li, li)

// Рекомендация: в 99% случаев используйте Element-версии

Обход дерева

// Обход всех дочерних элементов
function walkChildren(element, callback) {
  for (const child of element.children) {
    callback(child);
  }
}

// Рекурсивный обход всего поддерева
function walkTree(element, callback) {
  callback(element);
  for (const child of element.children) {
    walkTree(child, callback);
  }
}

walkTree(document.body, (el) => {
  console.log(el.tagName, el.className);
});

// TreeWalker — встроенный API для обхода
const walker = document.createTreeWalker(
  document.body,
  NodeFilter.SHOW_ELEMENT, // только элементы
  {
    acceptNode: (node) =>
      node.classList.contains('highlight')
        ? NodeFilter.FILTER_ACCEPT
        : NodeFilter.FILTER_SKIP
  }
);

let node;
while (node = walker.nextNode) {
  console.log(node.textContent);
}

Навигация по таблицам

// У таблиц есть специальные свойства навигации
const table = document.querySelector('table');

table.rows;              // HTMLCollection всех <tr>
table.caption;           // элемент <caption>
table.tHead;             // элемент <thead>
table.tFoot;             // элемент <tfoot>
table.tBodies;           // коллекция <tbody>

// Строка
const row = table.rows[0];
row.cells;               // HTMLCollection ячеек строки
row.rowIndex;            // индекс строки в таблице
row.sectionRowIndex;     // индекс внутри секции (thead/tbody/tfoot)

// Ячейка
const cell = row.cells[0];
cell.cellIndex;          // индекс ячейки в строке

Навигация по формам

// Формы
document.forms;                    // все формы
document.forms[0];                 // первая форма
document.forms.myForm;             // форма с name="myForm"

const form = document.forms[0];
form.elements;                     // все элементы формы
form.elements.email;               // элемент с name="email"
form.elements['user-name'];        // элемент с name="user-name"

// Обратная ссылка
const input = form.elements.email;
input.form;                        // ссылка на родительскую <form>

Частые ошибки

1. Путаница между childNodes и children

const list = document.querySelector('ul');

// childNodes включает текстовые узлы!
list.childNodes[0].tagName; // undefined (это Text-узел)

// children — только Element-узлы
list.children[0].tagName;   // "LI"

2. parentNode vs parentElement

// Для обычных элементов разницы нет
const div = document.querySelector('div');
div.parentNode === div.parentElement; // true

// Разница на уровне document
document.documentElement.parentNode;    // #document
document.documentElement.parentElement; // null (document — не Element)

3. siblings могут быть null

const first = document.querySelector('li:first-child');
console.log(first.previousElementSibling); // null — нет предыдущего

const last = document.querySelector('li:last-child');
console.log(last.nextElementSibling);      // null — нет следующего

// Всегда проверяйте на null
if (first.nextElementSibling) {
  first.nextElementSibling.classList.add('highlight');
}

Практика

  1. Обойди все <li> элементы списка через children и выведи текст
  2. Для каждого элемента выведи его родителя и соседей
  3. Реализуй рекурсивный обход DOM-дерева с подсчётом глубины
  4. Используй навигацию по таблице для раскрашивания чётных строк

Связанные темы

Ресурсы