DOM-дерево: узлы и типы узлов
DOM-дерево — иерархическая структура объектов (узлов), представляющих HTML-документ в памяти браузера; каждый узел имеет тип (Element, Text, Comment и др.) и набор свойств и методов.
Зачем нужно
Понимание типов узлов DOM объясняет поведение childNodes vs children, nodeValue vs textContent, почему пробелы между тегами создают Text-узлы. Без этого знания легко получить неожиданные результаты при обходе дерева или работе с childNodes. Знание интерфейсов Node, Element, HTMLElement помогает правильно применять методы API.
Где используется
- Обход DOM-дерева: рекурсивный поиск, фильтрация узлов по типу
- Работа с
childNodes: нужно фильтровать текстовые узлы/комментарии - Клонирование и перемещение узлов:
cloneNode(true/false) - Работа с шаблонами:
<template>содержит DocumentFragment - Серверный рендеринг: понимание структуры для гидрации
Основной контент
Иерархия интерфейсов
EventTarget
└── Node
├── Document (nodeType: 9)
├── DocumentFragment (nodeType: 11)
├── Element (nodeType: 1)
│ └── HTMLElement
│ ├── HTMLDivElement
│ ├── HTMLInputElement
│ └── ...
├── Text (nodeType: 3)
├── Comment (nodeType: 8)
└── ProcessingInstruction (nodeType: 7)
Типы узлов и nodeType
// Константы Node
console.log(Node.ELEMENT_NODE); // 1
console.log(Node.TEXT_NODE); // 3
console.log(Node.COMMENT_NODE); // 8
console.log(Node.DOCUMENT_NODE); // 9
console.log(Node.DOCUMENT_FRAGMENT_NODE); // 11
// Определение типа узла
function describeNode(node) {
switch (node.nodeType) {
case Node.ELEMENT_NODE:
return `Элемент: <${node.tagName.toLowerCase()}>`;
case Node.TEXT_NODE:
return `Текст: "${node.nodeValue.trim()}"`;
case Node.COMMENT_NODE:
return `Комментарий: ${node.nodeValue}`;
default:
return `Узел типа ${node.nodeType}`;
}
}
childNodes vs children
<ul id="list">
<li>Первый</li>
<!-- Комментарий -->
<li>Второй</li>
</ul>
const list = document.getElementById('list');
// childNodes — ВСЕ дочерние узлы (включая Text и Comment)
console.log(list.childNodes.length); // 7: \n, li, \n, comment, \n, li, \n
// children — только дочерние Element-узлы
console.log(list.children.length); // 2: два <li>
// Фильтрация по типу
const elementChildren = [...list.childNodes]
.filter(n => n.nodeType === Node.ELEMENT_NODE);
// или проще:
const elements = [...list.children];
Ключевые свойства узлов
const div = document.querySelector('div');
const textNode = div.firstChild; // Text-узел
// Свойства Node (у всех узлов)
div.nodeType; // 1
div.nodeName; // 'DIV'
div.nodeValue; // null (у элементов)
textNode.nodeValue; // текст (у Text-узлов)
// textContent — текст всего поддерева (работает у всех Node)
div.textContent = 'Новый текст'; // заменяет всё содержимое текстом
// Свойства Element
div.tagName; // 'DIV'
div.innerHTML; // HTML-содержимое как строка
div.outerHTML; // HTML-представление самого элемента
// Навигация по дереву
div.parentNode; // родительский Node (может быть Document)
div.parentElement; // родительский Element (null у <html>)
div.firstChild; // первый Node (может быть Text)
div.firstElementChild; // первый Element
div.lastElementChild; // последний Element
div.nextSibling; // следующий Node
div.nextElementSibling; // следующий Element
Создание и клонирование узлов
// Создание элементов
const el = document.createElement('div');
el.className = 'card';
el.dataset.id = '42';
// Создание текстового узла
const text = document.createTextNode('Привет');
el.appendChild(text);
// Создание комментария
const comment = document.createComment('TODO: убрать позже');
document.body.appendChild(comment);
// Клонирование
const original = document.getElementById('template-item');
const clone = original.cloneNode(true); // true — глубокое (с дочерними)
const shallow = original.cloneNode(false); // false — только сам узел
DocumentFragment
// DocumentFragment — «виртуальный» контейнер, не часть DOM
const fragment = document.createDocumentFragment;
for (let i = 0; i < 100; i++) {
const li = document.createElement('li');
li.textContent = `Пункт ${i}`;
fragment.appendChild(li); // нет reflow
}
document.getElementById('list').appendChild(fragment); // один reflow
// После вставки fragment пуст — дочерние перешли в DOM
Частые ошибки
childNodesвместоchildren: пробелы между тегами создают Text-узлы —childNodes[0]часто оказывается текстовым узлом\n, а не первым элементом.- Модификация
childNodesво время итерации:childNodes— живая коллекция. Итерация по ней с одновременным удалением элементов пропускает узлы — преобразуйте в массив:[...el.childNodes]. nodeValueу Element-узлов: у элементовnodeValue === null. Для получения текста элемента используйтеtextContent.