Коротка відповідь
middleФункція traverseDOM(node, cb) рекурсивно обходить дерево, викликаючи cb для кожного елемента. Приклад: function traverseDOM(node, cb){cb(node);node.childNodes.forEach(child=>traverseDOM(child, cb));}
Повне пояснення
Що це і навіщо
Рекурсивний обход DOM‑дерева потрібен, коли треба виконати дію над усіма елементами певної гілки (наприклад, оновити атрибути, збирати дані).
Принцип роботи / алгоритм
- Викликати callback для поточного вузла.
- Перебрати
childNodesі рекурсивно застосувати ту ж функцію. - Завершити, коли досягнуто листа (node.childNodes.length===0).
Реалізація (JS)
function traverseDOM(node, cb){
if(!node) return;
cb(node);
node.childNodes.forEach(child=>traverseDOM(child, cb));
}
Тестування
test('traverseDOM visits all nodes', () => {
const div = document.createElement('div');
div.innerHTML = '<span>1</span><p>2</p>';
const visited = [];
traverseDOM(div, n=>visited.push(n.tagName));
expect(visited).toEqual(['DIV','SPAN','P']);
});
Проблеми та edge cases
- Текстові вузли –
node.nodeType===3; callback може їх ігнорувати. - Циклічні структури – DOM не має циклів, але при маніпуляціях треба уникати рекурсивного додавання себе.
- Велика глибина – перевищення стеку; можна замінити на ітеративний стек.
- Селектори – якщо потрібен лише елементи, перевіряти
node.nodeType===1. - Скорочення – використання
for...ofзамістьforEachдля кращої продуктивності.
Оптимізація та продуктивність
- Використовувати
for...ofзамістьArray.prototype.forEach, бо перетворює NodeList у масив. - Якщо callback не змінює дерево, можна кешувати
childNodesперед рекурсією.
Без JS / архітектурні альтернативи
Для простих задач можна застосувати CSS‑селектор querySelectorAll('*') і обробити результати через for...of. Якщо потрібна реактивність, можна використати MutationObserver.
Cheatsheet + follow-up
- Cheatsheet:
node.childNodes→ усі вузли,node.children→ лише елементи. - Interview: Як уникнути перевищення стеку при глибокому DOM? – використати ітеративний стек або Tail‑call оптимізацію (не підтримується в браузерах).