Разница между 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 обновятся автоматически!

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

Ресурсы