Разница между childNodes и children
childNodesвозвращает все дочерние узлы (элементы, текст, комментарии), аchildren— только дочерние HTML-элементы без текстовых узлов и комментариев.
Зачем нужно
При работе с DOM легко получить неожиданный результат, итерируя childNodes — пробелы между тегами создают текстовые узлы (nodeType === 3). Понимание разницы между коллекциями позволяет выбрать правильный инструмент и избежать ошибок при обходе структуры страницы.
Где используется
- Обход потомков элемента в DOM-манипуляциях
- Проверка наличия и количества дочерних элементов
- Работа с шаблонами, динамическим контентом
- Алгоритмы, работающие с деревом DOM
Сравнение на примере
<ul id="list">
<li>Первый</li>
<li>Второй</li>
<!-- Комментарий -->
<li>Третий</li>
</ul>
const list = document.getElementById('list');
// childNodes — ВСЕ узлы (включая текст и комментарии)
console.log(list.childNodes);
// NodeList(9) [text, li, text, li, text, comment, text, li, text]
// text-узлы — это переносы строк и пробелы!
console.log(list.childNodes.length); // 9
// children — только HTML-элементы (Element nodes)
console.log(list.children);
// HTMLCollection(3) [li, li, li]
console.log(list.children.length); // 3
Типы узлов (nodeType)
const node = document.getElementById('list').childNodes[0];
node.nodeType; // число:
// 1 — Element (div, p, li...)
// 3 — Text (текст между тегами, включая пробелы)
// 8 — Comment (<!-- ... -->)
// 9 — Document
// 11 — DocumentFragment
Итерация
const ul = document.querySelector('ul');
// childNodes — псевдомассив NodeList, можно forEach
ul.childNodes.forEach(node => {
if (node.nodeType === Node.ELEMENT_NODE) { // 1
console.log('Элемент:', node.tagName);
}
if (node.nodeType === Node.TEXT_NODE) {
console.log('Текст:', JSON.stringify(node.textContent));
}
});
// children — HTMLCollection, нет forEach (только в старых браузерах)
// Правильно: Array.from или spread
Array.from(ul.children).forEach(el => {
console.log(el.textContent);
});
// или:
[...ul.children].forEach(el => console.log(el.textContent));
Связанные свойства
const el = document.querySelector('#parent');
// Первый/последний дочерний узел (любой тип)
el.firstChild; // может быть текстовым узлом!
el.lastChild;
// Первый/последний дочерний ЭЛЕМЕНТ
el.firstElementChild; // всегда Element или null
el.lastElementChild;
// Соседи
el.nextSibling; // любой тип узла
el.nextElementSibling; // только Element
el.previousSibling;
el.previousElementSibling;
// Родитель
el.parentNode; // любой тип (Document, Element, Fragment)
el.parentElement; // только HTMLElement или null
Практический пример
// Удалить все пустые текстовые узлы (нормализация)
function removeTextNodes(element) {
element.childNodes.forEach(node => {
if (node.nodeType === Node.TEXT_NODE && !node.textContent.trim()) {
node.remove();
}
});
}
// Получить только элементы-дети определённого типа
function getChildrenByTag(parent, tag) {
return [...parent.children].filter(
child => child.tagName.toLowerCase() === tag.toLowerCase()
);
}
const divs = getChildrenByTag(document.body, 'div');
Частые ошибки
1. Итерация childNodes и попытка вызвать методы Element
el.childNodes.forEach(node => {
node.classList.add('active'); // TypeError: node.classList is undefined
// classList есть только у Element-узлов!
if (node.nodeType === 1) node.classList.add('active'); // правильно
});
2. HTMLCollection не живёт во времени как NodeList
const children = el.children; // HTMLCollection — живая коллекция
const nodes = el.childNodes; // NodeList — тоже живая
const staticList = el.querySelectorAll('li'); // NodeList — статическая (не обновляется)
// При DOM-мутации HTMLCollection и childNodes обновятся автоматически!