#Что происходит и как распознать неисправность
MutationObserver вызывает callback при соответствующих изменениях дерева DOM. Если внутри callback менять те же атрибуты или дочерние узлы, которые он наблюдает, можно создавать цикл обновлений, рост памяти и зависание интерфейса.
#Как отличить причины
| Возможная причина | Как узнать | Что делать |
|---|---|---|
| Callback изменяет наблюдаемый атрибутНаблюдатель слушает attributes и постоянно записывает одно и то же значение. | Вызов callback повторяется после собственной правки. | Сравнивайте старое и нужное состояние перед записью. |
| В область наблюдения попадает служебный узелОбновление счетчика/лога внутри subtree вызывает новые события. | Мутации относятся к отладочному элементу. | Вынесите служебный узел за наблюдаемое дерево. |
| Несколько экземпляров наблюдателяПосле перерисовки создаются дополнительные observers без disconnect. | Число callback растёт после каждого mount. | Создавайте observer один раз, отключайте при уничтожении. |
#Пошаговая диагностика и исправление
- Сначала остановите зависающий сценарийЗакройте проблемную вкладку браузера либо остановите выполнение скрипта через инструменты разработчика, если она не отвечает. Откройте тестовую копию страницы, чтобы не изменять данные пользователей. Не пытайтесь бесконечно продолжать исполнение callback, если браузер уже потребляет много памяти.
- Найдите точку создания MutationObserverВ редакторе нажмите Ctrl+Shift+F, найдите
new MutationObserverиobserve(. Запишите, на каком узле выполняется наблюдение и какие опции включены:attributes,childList,characterData,subtree. Если наблюдение ведётся за document.body целиком, уточните, действительно ли нужна столь широкая область. - Посмотрите, какую мутацию вызывает обработчикДобавьте в тесте ограниченное логирование
records.map(r => [r.type, r.target.nodeName])без вывода текста конфиденциальных узлов. Если callback снова и снова обрабатывает одну и ту же служебную метку, сам создаёт новый цикл. Удалите временный лог после проверки, он тоже может создавать DOM при выводе в страницу. - Проверьте состояние до изменения DOMПеред
element.textContent = ...илиsetAttributeсравните текущее значение с целевым и выполняйте запись только при реальном изменении. Например, если текст уже равен нужному, немедленно выходите из callback. Это устраняет повторную мутацию при идемпотентной операции. - Сузьте настройки наблюденияЕсли нужно следить только за добавлением карточек, используйте
{childList: true, subtree: true}на контейнере карточек, не включайте attributes без нужды. Для одного атрибута задайтеattributeFilter: ["data-state"]. Не выбирайте одновременно все виды изменений на body «на всякий случай». - При особых обновлениях временно отключайте наблюдениеЕсли обработчик обязан значительно менять наблюдаемый узел, вызовите
observer.disconnect(), выполните изменение и снова вызовитеobserver.observe(target, options). Учитывайте, что мутации за время отключения не будут зафиксированы; этот приём годится только если потеря этих событий допустима, и нужно сохранять точные options для повторного включения. - Уберите лишние экземпляры при повторном запускеЕсли компонент удаляется и создаётся заново, в обработчике уничтожения вызовите disconnect для его observer. Не создавайте новый MutationObserver при каждой мутации. После серии из десяти изменений убедитесь, что число вызовов конечно и не растёт пропорционально числу предыдущих перерисовок.
- Проверьте устойчивость и возврат к старой версииОткройте страницу с F12 → вкладка «Производительность» (Performance), воспроизведите несколько добавлений/удалений узлов. Интерфейс должен оставаться отзывчивым, а в консоли не должно быть шквала одинаковых сообщений. Если важные изменения стали пропускаться, восстановите прежнюю область наблюдения из копии и продумайте прямое обновление без observer.
#Команды и примеры для проверки
const target = document.querySelector("#items");
if (!target) throw new Error("#items не найден");
const observer = new MutationObserver(() => {
const count = target.children.length;
const output = document.querySelector("#count");
if (output && output.textContent !== String(count)) {
output.textContent = String(count); // #count вне #items
}
});
observer.observe(target, {childList: true});
// При уничтожении компонента: observer.disconnect();#Как проверить результат и что делать дальше
Наблюдатель реагирует только на нужные изменения и не создаёт новых событий из-за своей работы. Если вы контролируете код, который меняет DOM, часто надёжнее вызывать обновление напрямую без MutationObserver.