JavaScript WindowsLinuxmacOS

JavaScript: MutationObserver создаёт бесконечный цикл обновления DOM

  • Обновлено
  • Чтение ≈ 3 минуты

Краткий ответ

MutationObserver бесконечно реагирует на собственные изменения DOM: определите наблюдаемую область, изменяйте только нужные узлы и прекращайте наблюдение перед опасной записью.

В этой статье
  1. Что происходит и как распознать неисправность
  2. Как отличить причины
  3. Пошаговая диагностика и исправление
  4. Команды и примеры для проверки
  5. Как проверить результат и что делать дальше

#Что происходит и как распознать неисправность

MutationObserver вызывает callback при соответствующих изменениях дерева DOM. Если внутри callback менять те же атрибуты или дочерние узлы, которые он наблюдает, можно создавать цикл обновлений, рост памяти и зависание интерфейса.

#Как отличить причины

Возможная причинаКак узнатьЧто делать
Callback изменяет наблюдаемый атрибутНаблюдатель слушает attributes и постоянно записывает одно и то же значение.Вызов callback повторяется после собственной правки.Сравнивайте старое и нужное состояние перед записью.
В область наблюдения попадает служебный узелОбновление счетчика/лога внутри subtree вызывает новые события.Мутации относятся к отладочному элементу.Вынесите служебный узел за наблюдаемое дерево.
Несколько экземпляров наблюдателяПосле перерисовки создаются дополнительные observers без disconnect.Число callback растёт после каждого mount.Создавайте observer один раз, отключайте при уничтожении.

#Пошаговая диагностика и исправление

  1. Сначала остановите зависающий сценарийЗакройте проблемную вкладку браузера либо остановите выполнение скрипта через инструменты разработчика, если она не отвечает. Откройте тестовую копию страницы, чтобы не изменять данные пользователей. Не пытайтесь бесконечно продолжать исполнение callback, если браузер уже потребляет много памяти.
  2. Найдите точку создания MutationObserverВ редакторе нажмите Ctrl+Shift+F, найдите new MutationObserver и observe(. Запишите, на каком узле выполняется наблюдение и какие опции включены: attributes, childList, characterData, subtree. Если наблюдение ведётся за document.body целиком, уточните, действительно ли нужна столь широкая область.
  3. Посмотрите, какую мутацию вызывает обработчикДобавьте в тесте ограниченное логирование records.map(r => [r.type, r.target.nodeName]) без вывода текста конфиденциальных узлов. Если callback снова и снова обрабатывает одну и ту же служебную метку, сам создаёт новый цикл. Удалите временный лог после проверки, он тоже может создавать DOM при выводе в страницу.
  4. Проверьте состояние до изменения DOMПеред element.textContent = ... или setAttribute сравните текущее значение с целевым и выполняйте запись только при реальном изменении. Например, если текст уже равен нужному, немедленно выходите из callback. Это устраняет повторную мутацию при идемпотентной операции.
  5. Сузьте настройки наблюденияЕсли нужно следить только за добавлением карточек, используйте {childList: true, subtree: true} на контейнере карточек, не включайте attributes без нужды. Для одного атрибута задайте attributeFilter: ["data-state"]. Не выбирайте одновременно все виды изменений на body «на всякий случай».
  6. При особых обновлениях временно отключайте наблюдениеЕсли обработчик обязан значительно менять наблюдаемый узел, вызовите observer.disconnect(), выполните изменение и снова вызовите observer.observe(target, options). Учитывайте, что мутации за время отключения не будут зафиксированы; этот приём годится только если потеря этих событий допустима, и нужно сохранять точные options для повторного включения.
  7. Уберите лишние экземпляры при повторном запускеЕсли компонент удаляется и создаётся заново, в обработчике уничтожения вызовите disconnect для его observer. Не создавайте новый MutationObserver при каждой мутации. После серии из десяти изменений убедитесь, что число вызовов конечно и не растёт пропорционально числу предыдущих перерисовок.
  8. Проверьте устойчивость и возврат к старой версииОткройте страницу с F12 → вкладка «Производительность» (Performance), воспроизведите несколько добавлений/удалений узлов. Интерфейс должен оставаться отзывчивым, а в консоли не должно быть шквала одинаковых сообщений. Если важные изменения стали пропускаться, восстановите прежнюю область наблюдения из копии и продумайте прямое обновление без observer.

#Команды и примеры для проверки

Текст
Здесь #count должен находиться за пределами наблюдаемого #items, иначе при расширении опций возможны новые события. Вызов disconnect обязателен при уничтожении интерфейса.
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.

Источники и обновления

Последнее обновление статьи: 2 ноября 2026. Версии программ и интерфейсы меняются — если шаги не совпадают с вашей версией, сверяйтесь с документацией производителя.