W3docs

JavaScript MutationObserver API

Изучите JavaScript MutationObserver API: наблюдение за изменениями DOM, методы observe/disconnect/takeRecords, MutationRecord, паттерны и примеры.

MutationObserver API в JavaScript позволяет следить за частью DOM и запускать callback-функцию всякий раз, когда что-то внутри изменяется — добавляется или удаляется узел, редактируется атрибут или обновляется текстовое содержимое. В отличие от устаревших mutation events, которые он заменил, MutationObserver группирует изменения и доставляет их асинхронно, не блокируя страницу и не генерируя отдельное событие для каждой мелкой правки.

В этом руководстве рассматривается, за чем можно наблюдать, три метода, которые предоставляет каждый observer, структура объекта MutationRecord, реальные сценарии использования, распространённые ловушки и интерактивный пример, который можно запустить.

Когда использовать MutationObserver?

MutationObserver применяют тогда, когда контент, которым вы не управляете, изменяется и вам нужно на это реагировать:

  • Сторонний / встраиваемый контент — виджет, реклама или блок, отрендеренный CMS, появляется в DOM и его нужно стилизовать или дополнить.
  • Ожидание появления элемента — ожидание узла, который фреймворк отрендерит позже, вместо опроса через setInterval.
  • Синхронизация UI с изменениями атрибутов — реакция на изменение class, style, disabled или атрибута data-* на элементе, к которому нельзя добавить обработчик.
  • Авторесайз или перемерка — пересчёт разметки при добавлении дочерних элементов в контейнер.
  • Синхронизация модели с текстом в contenteditable.

Если вам нужно только знать, когда элемент входит или покидает область видимости, используйте IntersectionObserver — он создан специально для этой задачи и обходится дешевле.

Три метода

Каждый экземпляр observer предоставляет ровно три метода:

МетодЧто делает
observe(target, options)Начинает наблюдение за target с помощью объекта options. Вызовите снова с другим target, чтобы наблюдать за несколькими узлами одним observer.
disconnect()Прекращает наблюдение за всеми целями. Callback больше не будет вызываться. Всегда вызывайте этот метод, когда работа завершена, чтобы избежать утечек памяти.
takeRecords()Синхронно возвращает (и очищает) все ожидающие MutationRecords, которые ещё не были доставлены в callback. Полезно вызвать непосредственно перед disconnect(), чтобы не потерять последнюю порцию данных.

Объект options, передаваемый в observe(), должен содержать хотя бы одно из полей childList, attributes или characterData, иначе вызов бросит TypeError.

ОпцияЗначение
childListСледить за добавлением/удалением прямых дочерних узлов.
attributesСледить за изменениями атрибутов.
characterDataСледить за изменениями данных текстового узла.
subtreeРаспространить наблюдение на всех потомков, а не только на target.
attributeOldValueЗаписывать предыдущее значение атрибута (подразумевает attributes).
characterDataOldValueЗаписывать предыдущее текстовое значение (подразумевает characterData).
attributeFilterArray имён атрибутов для наблюдения — остальные игнорируются.

Пример Mutation Observer

Вот базовый пример, демонстрирующий работу Mutation Observer путём визуального отображения изменений в DOM.

<!DOCTYPE html>
<html>
<head>
  <title>Exploring DOM Changes: Live Examples with Mutation Observers</title>
</head>
<body>
  <div id="target" style="background-color: lightgray; padding: 10px;">
    Watch this space for changes!
  </div>
  <button style="margin-top: 10px;" onclick="addNewElement(); changeAttribute();">Add New Element and Change Color</button>
  <div id="log" style="margin-top: 20px;"></div>

  <script>
    // Get the element to observe
    const targetNode = document.getElementById('target');

    // Define configurations for the observer
    const config = { attributes: true, childList: true, subtree: true, attributeOldValue: true };

    // Callback function to execute when mutations are observed
    const callback = function(mutationsList, observer) {
      for (const mutation of mutationsList) {
        const message = document.createElement('p');
        if (mutation.type === 'childList') {
          message.textContent = 'A child node has been added or removed.';
          message.style.color = 'green';
        } else if (mutation.type === 'attributes') {
          message.textContent = 'The ' + mutation.attributeName + ' attribute was modified.';
          message.style.color = 'blue';
        }
        document.getElementById('log').appendChild(message);
      }
    };

    // Create an observer instance linked to the callback function
    const observer = new MutationObserver(callback);

    // Start observing the target node for configured mutations
    observer.observe(targetNode, config);

    // Function to add new elements
    function addNewElement() {
      const newElement = document.createElement('div');
      newElement.textContent = 'New element added!';
      targetNode.appendChild(newElement);
    }

    // Function to change attributes
    function changeAttribute() {
      const currentColor = targetNode.style.backgroundColor;
      targetNode.style.backgroundColor = currentColor === 'lightgray' ? 'lightblue' : 'lightgray';
    }
  </script>
</body>
</html>

Этот пример демонстрирует использование Mutation Observer для обнаружения изменений в объектной модели документа (DOM) веб-страницы и реагирования на них. Вот что делает каждая часть JavaScript и что вы увидите при взаимодействии с примером:

  1. Настройка Mutation Observer:
    • Целевой узел: DOM-элемент, за которым вы хотите наблюдать. В данном случае это div с ID target.
    • Конфигурация: определяет, какие типы изменений нужно отслеживать:
      • attributes: observer будет следить за изменениями атрибутов (например, style или class).
      • childList: будет проверять добавление или удаление дочерних элементов (например, новых div).
      • subtree: обеспечивает проверку не только целевого элемента, но и его потомков. Обратите внимание, что subtree действует только при включённом childList или attributes.
      • attributeOldValue: записывает предыдущее значение изменённого атрибута (полезно для отслеживания изменений).
  2. Определение callback-функции:
    • Эта функция выполняется каждый раз, когда observer обнаруживает изменение согласно заданной конфигурации.
    • Она перебирает все обнаруженные мутации и создаёт сообщение журнала для каждой из них:
      • Если дочерний элемент добавлен или удалён, выводится "A child node has been added or removed." зелёным текстом.
      • Если атрибут изменился (например, цвет фона), выводится "The mutation.attributeName attribute was modified." синим текстом.
  3. Экземпляр Observer:
    • Mutation Observer создаётся и связывается с callback-функцией.
  4. Начало наблюдения:
    • Observer начинает отслеживать изменения в div с ID target согласно указанной конфигурации.
  5. Интерактивные функции:
    • Добавление нового элемента: при нажатии кнопки добавляется новый div с текстом "New element added!" внутри div с ID target.
    • Изменение атрибута: то же нажатие кнопки переключает цвет фона div с ID target между 'lightgray' и 'lightblue'. Примечание: хотя встроенный onclick подходит для данного примера, в продакшене рекомендуется использовать addEventListener для более чистого разделения кода.

Ожидаемые результаты:

  • Добавление нового элемента:
    • При каждом нажатии кнопки добавляется новый div. Это запускает проверку childList observer, и вы увидите зелёное сообщение "A child node has been added or removed."
  • Изменение атрибута:
    • То же нажатие кнопки изменит цвет фона div с ID target. Это запускает проверку атрибутов observer. Вы увидите синее сообщение с указанием изменённого атрибута ("The style attribute was modified.").

Данный пример наглядно показывает, как Mutation Observers могут использоваться для мониторинга и записи изменений в DOM, обеспечивая обратную связь в реальном времени о происходящем на странице.

Чтение MutationRecord

Callback получает array объектов MutationRecord — по одному на каждое обнаруженное изменение. Наиболее полезные свойства:

  • type"childList", "attributes" или "characterData".
  • target — узел, которого коснулась мутация.
  • addedNodes / removedNodesNodeLists вставленных/удалённых узлов (для childList).
  • attributeName — изменённый атрибут (для attributes).
  • oldValue — предыдущее значение, но только если было включено attributeOldValue или characterDataOldValue.
const observer = new MutationObserver((records) => {
  for (const record of records) {
    if (record.type === "attributes") {
      console.log(`${record.attributeName} changed from "${record.oldValue}"`);
    } else if (record.type === "childList") {
      console.log(`+${record.addedNodes.length} / -${record.removedNodes.length} nodes`);
    }
  }
});

Практический паттерн: ожидание появления элемента

Один из распространённых реальных сценариев — выполнение Promise в момент появления узла в DOM, что намного лучше опроса. Observer отключается сам, как только находит элемент:

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(); // stop watching once found
        resolve(el);
      }
    });

    observer.observe(document.body, { childList: true, subtree: true });
  });
}

// Usage with async/await:
// const card = await waitForElement(".lazy-card");

Это хорошо сочетается с async/await и Promises.

Ловушки и рекомендации

  • Callback асинхронен и пакетный. Мутации ставятся в очередь как microtasks и доставляются после завершения текущего скрипта — см. microtasks и event loop. Вы не получите запись для каждого отдельного изменения — они приходят группой.
  • Ваш callback выполняется после изменения. Вас уведомляют о том, что уже произошло — отменить или предотвратить мутацию, как это делает preventDefault() для события, нельзя.
  • oldValue требует явного включения. Если вы читаете record.oldValue без включения attributeOldValue/characterDataOldValue, значение будет null.
  • Избегайте изменения наблюдаемого поддерева внутри callback, если это не запланировано — это может породить новые записи и создать петлю обратной связи.
  • Всегда вызывайте disconnect(). Активный observer удерживает свою цель в памяти, поэтому забытый disconnect() приводит к утечке памяти. Отключайтесь при размонтировании компонента или по завершении работы.
  • Вызывайте takeRecords() перед отключением, если важна последняя порция данных — disconnect() удаляет недоставленные записи.

Заключение

Mutation Observers — важный инструмент в арсенале JavaScript, предлагающий динамические решения для эффективного управления изменениями DOM. Они позволяют разработчикам создавать отзывчивые, интерактивные веб-приложения, которые плавно реагируют на действия пользователя и программные изменения DOM. При всей своей мощи, Mutation Observers следует использовать взвешенно, чтобы сохранять оптимальную производительность и качество пользовательского опыта. Тщательно выбирая, какие мутации отслеживать, минимизируя накладные расходы в callback-функциях и вызывая observer.disconnect(), когда observer больше не нужен, разработчики могут применять Mutation Observers для расширения функциональности сайта без ущерба для эффективности. Понимание и применение этих принципов позволяет создавать продвинутые, удобные для пользователя веб-интерфейсы, выделяющиеся в современном цифровом пространстве.

Практика

Практика
Какие из следующих утверждений верны относительно JavaScript Mutation Observer?
Какие из следующих утверждений верны относительно JavaScript Mutation Observer?
Was this page helpful?