Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «MutationObserver». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**`MutationObserver` це вбудований у браузер API, який стежить за змінами в DOM: додаванням і видаленням дочірніх вузлів, зміною атрибутів і зміною тексту, і повідомляє про них колбеком.** Ви створюєте спостерігач з колбеком, викликаєте `observe(target, options)` і в опціях вмикаєте те, що вас цікавить: `childList`, `attributes`, `characterData`, `subtree`. Колбек отримує масив об'єктів `MutationRecord` з описом кожної зміни. Працює асинхронно, на мікротасках, і віддає зміни батчем, тому не блокує інтерфейс. Це сучасна заміна застарілим мутаційним подіям `DOMNodeInserted` і `DOMSubtreeModified`, які гальмували сторінку. ```javascript const observer = new MutationObserver((mutations) => { mutations.forEach((m) => console.log('Тип зміни:', m.type)); }); observer.observe(document.querySelector('#app'), { childList: true, attributes: true, subtree: true, }); ``` **Ключове:** `MutationObserver` це асинхронний «охоронець DOM»: `observe()` вмикає спостереження, `disconnect()` вимикає, а `takeRecords()` забирає накопичені записи.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**`MutationObserver` це спеціальний об'єкт, який спостерігає за елементом і повідомляє, коли в ньому щось змінилося: структура, атрибути або текст.** Він працює асинхронно, не блокує інтерфейс і викликає колбек, коли у вузлі, за яким стежимо, сталися зміни. ## Теорія ### TL;DR - `MutationObserver` стежить за змінами DOM і реагує на них у реальному часі. - Створюємо його з колбеком, запускаємо через `observer.observe(target, options)`. - Опції вмикають типи змін: `childList`, `attributes`, `characterData`, плюс `subtree` для всього піддерева. - Колбек отримує масив `MutationRecord`, кожен запис описує одну зміну. - Працює асинхронно, через чергу мікротасків, зміни приходять батчем, тому це дешево. - Керування: `observe()`, `disconnect()`, `takeRecords()`. - Це заміна застарілим подіям `DOMNodeInserted` і `DOMSubtreeModified`. ### Швидкий приклад ```javascript // 1. Створюємо спостерігач const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { console.log('Тип зміни:', mutation.type); }); }); // 2. Елемент, за яким будемо стежити const target = document.querySelector('#app'); // 3. Налаштування спостереження observer.observe(target, { childList: true, // стежити за додаванням і видаленням дочірніх елементів attributes: true, // стежити за зміною атрибутів subtree: true // стежити за всім усередині елемента }); ``` Тепер, якщо додати в `#app` новий елемент або змінити атрибут, у консолі з'явиться повідомлення про тип зміни. ### Приклад «наживо» ```javascript <div id="box">Привіт!</div> <button id="btn">Змінити</button> <script> const box = document.getElementById('box'); const btn = document.getElementById('btn'); const observer = new MutationObserver((mutations) => { mutations.forEach((m) => console.log(m)); }); observer.observe(box, { childList: true, attributes: true, characterData: true }); btn.addEventListener('click', () => { box.textContent = 'Текст змінено!'; box.setAttribute('data-status', 'updated'); }); </script> ``` Після кліку в консолі з'являться об'єкти `MutationRecord`, у яких буде інформація про: - тип зміни (`attributes`, `childList`, `characterData`); - який атрибут змінився; - старе і нове значення, якщо це увімкнено. ### Налаштування observe() ```javascript observer.observe(target, { childList: true, // додавання і видалення дочірніх елементів attributes: true, // зміна атрибутів characterData: true, // зміна тексту всередині вузлів subtree: true, // стежити за нащадками (усе дерево) attributeFilter: ['class', 'style'], // стежити тільки за цими атрибутами attributeOldValue: true, // зберігати старе значення атрибутів characterDataOldValue: true // зберігати старе значення тексту }); ``` Якщо не увімкнути жодного з `childList`, `attributes`, `characterData`, браузер кине помилку: спостерігачу немає за чим стежити. ### Що містить MutationRecord Кожен елемент масиву `mutations` це об'єкт `MutationRecord` з такими властивостями: | Властивість | Опис | | --- | --- | | `type` | тип зміни (`attributes`, `childList`, `characterData`) | | `target` | елемент, де сталася зміна | | `addedNodes` | додані елементи | | `removedNodes` | видалені елементи | | `attributeName` | ім'я зміненого атрибута | | `oldValue` | старе значення атрибута або тексту | ### Керування спостерігачем Щоб перестати стежити: ```javascript observer.disconnect(); ``` Щоб забрати вже накопичені зміни і за потреби знову запустити спостереження: ```javascript observer.takeRecords(); // повертає накопичені зміни observer.observe(target, { childList: true }); // можна увімкнути знову ``` `takeRecords()` віддає записи, які ще не потрапили в колбек, і очищує чергу. Це корисно перед `disconnect()`, щоб нічого не загубити. ### Практичні сценарії Дуже частий кейс, ловити нові елементи, додані JS-фреймворком (наприклад React, Vue або стороннім скриптом): ```javascript const container = document.querySelector('#feed'); const observer = new MutationObserver((mutations) => { mutations.forEach((m) => { m.addedNodes.forEach((node) => { if (node.nodeType === 1 && node.matches('.post')) { console.log('Новий пост:', node.textContent); } }); }); }); observer.observe(container, { childList: true, subtree: true }); ``` Тепер при динамічному додаванні `<div class="post">...</div>` JS миттєво відреагує. Другий типовий сценарій, автоматична реакція на зміну класу: ```javascript const box = document.querySelector('#box'); const observer = new MutationObserver((entries) => { for (const mutation of entries) { if (mutation.attributeName === 'class') { console.log('Клас змінився на:', box.className); } } }); observer.observe(box, { attributes: true }); ``` Це зручно для відстеження зміни стану, наприклад при анімаціях або toggle-ефектах. ### Продуктивність і підсумок - `MutationObserver` працює асинхронно, зміни «пакуються» і приходять одним батчем, а не на кожен символ чи піксель. - Це робить його дуже продуктивним навіть за великої кількості спостережень. - Але не варто зловживати `subtree: true` на всьому `document`, це може бути дорого. | Що робить | `MutationObserver` | | --- | --- | | Відстежує | Зміни в DOM (атрибути, текст, додавання і видалення елементів) | | Заміна для | Застарілих подій `DOMNodeInserted`, `DOMSubtreeModified` | | Працює | Асинхронно, через чергу мікротасків | | Використовується для | Динамічного UI, інтеграцій, спостереження за React або Vue, анімацій, кастомних компонентів | | Керування | `observe()`, `disconnect()`, `takeRecords()` | ### Типові помилки - **Викликати `observe()` без жодного з прапорців `childList`, `attributes`, `characterData`.** Браузер кине `TypeError`, бо спостерігати нема за чим. - **Очікувати синхронного виклику колбека.** Одразу після `element.setAttribute(...)` колбек ще не спрацював, зміни приходять на мікротасці. Якщо результат потрібен негайно, беріть `takeRecords()`. - **Змінювати DOM усередині колбека без запобіжників.** Власні зміни знову потраплять до спостерігача, і легко отримати нескінченний цикл. Рішення: тимчасово `disconnect()`, змінити, потім `observe()` знову. - **Вішати `subtree: true` на `document.body` заради однієї кнопки.** Спостерігач буде будити колбек на кожну дрібну зміну сторінки. Звужуйте `target` і додавайте `attributeFilter`. - **Забувати `disconnect()`.** Спостерігач тримає посилання на вузол і колбек, тому при видаленні компонента без відключення отримуєте витік пам'яті. - **Плутати з `IntersectionObserver` або `ResizeObserver`.** Перший стежить за видимістю елемента, другий за його розміром, а `MutationObserver` тільки за структурою DOM і атрибутами.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.