Skip to main content

MutationObserver

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 і атрибутами.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.