MutationObserver: наблюдение за DOM
MutationObserver— браузерный API для асинхронного наблюдения за изменениями в DOM-дереве: добавлением/удалением узлов, изменением атрибутов и текстового содержимого.
Зачем нужно
MutationObserver — замена устаревшим Mutation Events, работающий асинхронно (батчинг) и не блокирующий рендеринг. Используется когда нужно реагировать на изменения DOM, сделанные сторонним кодом (библиотеками, расширениями), или следить за динамически добавляемым контентом.
Где используется
- Инструменты разработчика и расширения браузера
- Отслеживание изменений в rich text редакторах
- Lazy loading: реагировать на появление элементов в DOM
- Интеграция с legacy-кодом, который напрямую модифицирует DOM
- Тесты: ожидать появление определённого элемента
Основной контент
Базовое использование
// Создаём наблюдатель
const observer = new MutationObserver((mutations) => {
mutations.forEach(mutation => {
console.log('Тип мутации:', mutation.type);
if (mutation.type === 'childList') {
mutation.addedNodes.forEach(node => {
console.log('Добавлен:', node);
});
mutation.removedNodes.forEach(node => {
console.log('Удалён:', node);
});
}
if (mutation.type === 'attributes') {
console.log(`Атрибут "${mutation.attributeName}" изменён`);
console.log('Старое значение:', mutation.oldValue);
}
});
});
// Подключаем к элементу
const target = document.getElementById('container');
observer.observe(target, {
childList: true, // добавление/удаление дочерних узлов
attributes: true, // изменение атрибутов
subtree: true, // наблюдать за всем поддеревом
attributeOldValue: true, // сохранять старое значение атрибута
characterData: true, // изменение текста
});
// Остановить наблюдение
observer.disconnect();
Наблюдение за добавлением элементов
// Ждём появления элемента в DOM (lazy init)
function waitForElement(selector) {
return new Promise(resolve => {
const existing = document.querySelector(selector);
if (existing) return resolve(existing);
const observer = new MutationObserver(() => {
const el = document.querySelector(selector);
if (el) {
observer.disconnect();
resolve(el);
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
});
}
waitForElement('#dynamic-widget').then(el => {
console.log('Элемент появился:', el);
el.addEventListener('click', handleClick);
});
Батчинг и производительность
// MutationObserver батчит изменения — callback вызывается один раз
// даже при множественных синхронных изменениях
const list = document.getElementById('list');
const observer = new MutationObserver(mutations => {
console.log(`Получено ${mutations.length} мутаций`);
});
observer.observe(list, { childList: true });
// Три добавления — одна запись в callback (батч)
for (let i = 0; i < 3; i++) {
const li = document.createElement('li');
li.textContent = `Пункт ${i}`;
list.appendChild(li);
}
// Один вызов callback с массивом из 3 мутаций
Частые ошибки
- Забыть вызвать
disconnect— наблюдатель держит ссылку на target, мешая сборщику мусора. Всегда отключайте в cleanup (при удалении компонента). - Наблюдение за
document.bodyсsubtree: true— очень дорого по производительности. Наблюдайте за минимально необходимым поддеревом. - Бесконечный цикл — если callback сам изменяет DOM, это вызывает новые мутации. Используйте флаг или проверяйте, что изменение сделано самим наблюдателем.