Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Що таке DOM?». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**DOM (Document Object Model)** - це живе дерево об'єктів, яке браузер будує з HTML в пам'яті, даючи JavaScript доступ до вмісту сторінки без перезавантаження. ```javascript const para = document.getElementById('demo'); para.textContent = 'Оновлено!'; // змінює текст, запускає перемальовування console.log(para.nodeName); // "P" ``` **Ключове:** HTML - статичний файл; DOM - це жива, змінювана версія його в пам'яті.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**DOM (Document Object Model)** - це живе дерево об'єктів, яке браузер будує з HTML-документа в пам'яті, дозволяючи JavaScript читати, змінювати, додавати і видаляти вузли без перезавантаження сторінки. ## Теорія ### TL;DR - DOM - це те, що браузер збудував з HTML і тримає в пам'яті як змінюване дерево об'єктів, а не статичний файл - Аналогія: HTML - роздрукований рецепт; DOM - жива кухня, де шеф-кухар може поміняти або додати інгредієнти прямо під час готування - Головна різниця: HTML парситься один раз і лежить на диску; DOM живе в пам'яті й постійно змінюється - Прямі DOM API - для дрібних скриптів і розширень; React або Preact - коли оновлення стають частими або складними ### Швидкий приклад ```html <!DOCTYPE html> <html> <body> <p id="demo">Оригінальний текст</p> <script> const para = document.getElementById('demo'); para.textContent = 'DOM змінив це!'; // Без перезавантаження console.log(para.nodeName); // "P" </script> </body> </html> ``` `getElementById` повертає живий об'єкт вузла. Зміна `textContent` одразу запускає перемальовування в рендерному пайплайні браузера. Це і є весь цикл. ### HTML проти DOM HTML - статичний файл розмітки. Браузер парсить його один раз і будує дерево DOM в пам'яті. Після цього вихідний файл більше не потрібен: те, що бачить користувач, - це DOM. Тому JavaScript може змінювати сторінку, не торкаючись вихідного файлу. DOM дає стандартизовані об'єкти вузлів: `ElementNode`, `TextNode`, `CommentNode`. У кожного є методи на кшталт `appendChild()`. Коли ти їх викликаєш, браузер автоматично ставить оновлення екрана в чергу. ### Типи вузлів Кожен елемент у дереві DOM - це вузол (node). Чотири типи трапляються найчастіше: - **Element node** (`<div>`, `<p>` тощо) - nodeType: 1 - **Text node** (текстовий вміст всередині тегів) - nodeType: 3 - **Comment node** (HTML-коментарі) - nodeType: 8 - **Document node** (корінь дерева, `document`) - nodeType: 9 Це важливо при переборі. `childNodes` повертає всі типи, включно з текстовими вузлами від пробілів між тегами. `children` повертає тільки вузли-елементи. Переплутаєш - отримаєш `Cannot set properties of undefined` на вузлі з пробілом. ### Коли використовувати - Валідація форми або перемикання класу - прямі DOM API підходять - Браузерні розширення або буклети - фреймворк просто недоступний - Швидке прототипування - без налаштування збірки - Часті або масові оновлення, наприклад рендер 1000 елементів списку - перейди на [бібліотеку з virtual DOM, як React](/questions/what-is-react); її reconciler батчить зміни і мінімізує reflow ### Що відбувається в браузері при мутації DOM Движок Blink у Chrome зберігає DOM як C++ об'єкти `Node`, доступні для JavaScript через WebIDL-прив'язки. Виклик `querySelector()` змушує V8 обходити дерево в глибину (O(n) у гіршому випадку). Мутація на кшталт `appendChild()` позначає відповідну гілку як брудну і ставить у чергу перерахунок стилів, layout (reflow), paint і compositing. Якщо групувати мутації всередині `requestAnimationFrame`, всі ці проходи зливаються в один - так отримують 60fps. Node.js не має DOM. Для тестування парсингу HTML у Node використовується `jsdom`. ### Типові помилки **Помилка 1: `innerHTML` з даними користувача** ```javascript // Неправильно: парсить і виконує розмітку element.innerHTML = userInput; // <script>alert('XSS')</script> запускається // Правильно: все трактується як звичайний текст element.textContent = userInput; ``` На код-рев'ю я бачу `innerHTML = userInput` набагато частіше, ніж хотілося б. Зазвичай це дедлайн-код, де людина просто хотіла щоб спрацювало, не подумавши про джерело даних. Для будь-яких даних від користувача використовуй `textContent` або `createTextNode()`. **Помилка 2: Не перевіряєш null** ```javascript // Неправильно const el = document.getElementById('nonexistent'); // null el.textContent = 'hi'; // TypeError: Cannot set property // Правильно if (el) el.textContent = 'hi'; ``` `getElementById` повертає `null`, якщо нічого не знайдено. Один непровірений результат ламає весь скрипт. **Помилка 3: Перебір живої колекції з мутацією** ```javascript // Неправильно: getElementsByClassName повертає живу HTMLCollection const items = document.getElementsByClassName('old-item'); Array.prototype.forEach.call(items, item => item.remove()); // пропускає кожен другий // Правильно: спочатку перетвори на статичний масив Array.from(document.getElementsByClassName('old-item')).forEach( item => item.remove() ); ``` `getElementsByClassName` і `getElementsByTagName` повертають живі колекції. Видалення елемента зміщує індекси, і цикл пропускає наступний сусід щоразу. `querySelectorAll` повертає статичний знімок і цієї проблеми не має. **Помилка 4: `children` проти `childNodes`** ```javascript // Працює: children містить тільки Element-вузли parent.children[0].style.color = 'red'; // Може впасти: childNodes[0] може бути TextNode з пробілом parent.childNodes[0].style.color = 'red'; // Cannot set properties of undefined ``` **Помилка 5: Не видаляєш обробники перед видаленням вузла** ```javascript const btn = document.createElement('button'); btn.addEventListener('click', handler); document.body.appendChild(btn); btn.remove(); // обробник залишається в пам'яті // Правильно btn.removeEventListener('click', handler); btn.remove(); ``` У single-page app, де елементи постійно створюються і видаляються, осиротілі обробники накопичуються у реальний витік пам'яті. ### Де зустрічається на практиці - **React**: `ReactDOM.render()` перетворює JSX на реальні DOM-мутації через `createElement` і `appendChild` - **jQuery** (легасі): `$(el).find()` обгортає `querySelectorAll` з крос-браузерною сумісністю - **Puppeteer**: `page.$eval()` виконує JavaScript на живому DOM для end-to-end тестів - **WordPress / SSR-додатки**: сервер рендерить HTML, потім клієнтський JavaScript гідратує DOM для інтерактивності ### Можливі питання на співбесіді **Q:** Яка різниця між `textContent` і `innerHTML`? **A:** `textContent` встановлює звичайний текст і екранує все. `innerHTML` парсить рядок як розмітку і може виконати скрипти. Для будь-яких даних від користувача завжди використовуй `textContent`. **Q:** Як `querySelector` обходить дерево? **A:** Обхід у глибину, pre-order, починаючи від кореня документа. Зупиняється на першому збігу. Тому він швидший за `querySelectorAll`, якщо потрібен один елемент. **Q:** Яка різниця між реальним DOM і [virtual DOM](/questions/what-is-virtual-dom)? **A:** Реальний DOM - фактичне дерево браузера. Virtual DOM - звичайний JavaScript-об'єкт, що його відображає. React порівнює старе і нове віртуальне дерево, потім застосовує мінімальний набір змін до реального DOM. **Q:** Які є типи вузлів у DOM? **A:** Основні: Element (nodeType 1), Text (nodeType 3), Comment (nodeType 8), Document (nodeType 9). Перевіряєш через `node.nodeType`. **Q (рівень senior):** Що відбувається в Blink, коли викликаєш `appendChild()`? **A:** Вузол вставляється в C++ дерево. Це позначає відповідну гілку як брудну і планує прохід перерахунку стилів, потім layout (reflow для геометрії), paint (малювання пікселів) і compositing. Якщо групувати мутації до наступного кадру через `requestAnimationFrame`, всі ці проходи зливаються в один - так отримуєш 60fps у DOM-важкому коді. ## Приклади ### Базовий: зміна текстового вмісту ```html <!DOCTYPE html> <html> <body> <p id="demo">Оригінальний текст</p> <script> const para = document.getElementById('demo'); para.textContent = 'Оновлено через JS'; // запускає перемальовування console.log(para.nodeName); // "P" </script> </body> </html> ``` Знайти вузол за ID і оновити його текст - найпоширеніша операція з DOM. Без перезавантаження, без фреймворку. ### Середній: живий список задач ```html <!DOCTYPE html> <html> <body> <ul id="todos"></ul> <input id="task" placeholder="Назва задачі" /> <button id="add">Додати</button> <script> const ul = document.getElementById('todos'); const input = document.getElementById('task'); document.getElementById('add').addEventListener('click', () => { const li = document.createElement('li'); li.textContent = input.value; // textContent захищає від XSS ul.appendChild(li); input.value = ''; }); </script> </body> </html> ``` Це те, що робить TodoMVC у своїй vanilla JS реалізації. `createElement` будує вузол, `appendChild` вставляє його, `textContent` тримає все безпечним. React зі своїм `useState` і JSX робить те саме під капотом, тільки з кроком diff перед тим як торкнутися реального DOM.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.