MutationObserver
MutationObserver це спеціальний об'єкт, який спостерігає за елементом і повідомляє, коли в ньому щось змінилося: структура, атрибути або текст. Він працює асинхронно, не блокує інтерфейс і викликає колбек, коли у вузлі, за яким стежимо, сталися зміни.
Теорія
TL;DR
MutationObserverстежить за змінами DOM і реагує на них у реальному часі.- Створюємо його з колбеком, запускаємо через
observer.observe(target, options). - Опції вмикають типи змін:
childList,attributes,characterData, плюсsubtreeдля всього піддерева. - Колбек отримує масив
MutationRecord, кожен запис описує одну зміну. - Працює асинхронно, через чергу мікротасків, зміни приходять батчем, тому це дешево.
- Керування:
observe(),disconnect(),takeRecords(). - Це заміна застарілим подіям
DOMNodeInsertedіDOMSubtreeModified.
Швидкий приклад
// 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 новий елемент або змінити атрибут, у консолі з'явиться повідомлення про тип зміни.
Приклад «наживо»
<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()
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 | старе значення атрибута або тексту |
Керування спостерігачем
Щоб перестати стежити:
observer.disconnect();Щоб забрати вже накопичені зміни і за потреби знову запустити спостереження:
observer.takeRecords(); // повертає накопичені зміни
observer.observe(target, { childList: true }); // можна увімкнути зновуtakeRecords() віддає записи, які ще не потрапили в колбек, і очищує чергу. Це корисно перед disconnect(), щоб нічого не загубити.
Практичні сценарії
Дуже частий кейс, ловити нові елементи, додані JS-фреймворком (наприклад React, Vue або стороннім скриптом):
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 миттєво відреагує.
Другий типовий сценарій, автоматична реакція на зміну класу:
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 і атрибутами.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.