Pull to refresh
2983.5
RUVDS.com
VDS/VPS-хостинг. Скидка 15% по коду HABR15

Как работает JS: отслеживание изменений в DOM с помощью MutationObserver

Reading time7 min
Views55K
Original author: Alexander Zlatkov
Сегодня, в переводе десятого материала из серии, посвящённой особенностям работы механизмов JavaScript, мы расскажем о том, как отслеживать изменения в DOM с помощью API MutationObserver.

Клиентские части веб-приложений становятся всё сложнее, требуют всё больше системных ресурсов. Происходит это по разным причинам, в частности из-за того, что таким приложениям нужны продвинутые интерфейсы, благодаря которым раскрываются их возможности, и из-за того, что им приходится выполнять сложные вычисления на стороне клиента.



Всё это ведёт к усложнению задачи контроля состояния интерфейсов приложений в процессе их жизненного цикла. Эта задача становится ещё масштабнее в том случае, если речь идёт о разработке чего-то вроде фреймворка или даже обычной библиотеки, когда, например, нужно реагировать на то, что происходит со страницей и выполнять какие-то действия, зависящие от DOM.

[Советуем почитать] Другие 19 частей цикла
Часть 1: Обзор движка, механизмов времени выполнения, стека вызовов
Часть 2: О внутреннем устройстве V8 и оптимизации кода
Часть 3: Управление памятью, четыре вида утечек памяти и борьба с ними
Часть 4: Цикл событий, асинхронность и пять способов улучшения кода с помощью async / await
Часть 5: WebSocket и HTTP/2+SSE. Что выбрать?
Часть 6: Особенности и сфера применения WebAssembly
Часть 7: Веб-воркеры и пять сценариев их использования
Часть 8: Сервис-воркеры
Часть 9: Веб push-уведомления
Часть 10: Отслеживание изменений в DOM с помощью MutationObserver
Часть 11: Движки рендеринга веб-страниц и советы по оптимизации их производительности
Часть 12: Сетевая подсистема браузеров, оптимизация её производительности и безопасности
Часть 12: Сетевая подсистема браузеров, оптимизация её производительности и безопасности
Часть 13: Анимация средствами CSS и JavaScript
Часть 14: Как работает JS: абстрактные синтаксические деревья, парсинг и его оптимизация
Часть 15: Как работает JS: классы и наследование, транспиляция в Babel и TypeScript
Часть 16: Как работает JS: системы хранения данных
Часть 17: Как работает JS: технология Shadow DOM и веб-компоненты
Часть 18: Как работает JS: WebRTC и механизмы P2P-коммуникаций
Часть 19: Как работает JS: пользовательские элементы

Обзор


MutationObserver — это Web API, предоставляемое современными браузерами и предназначенное для обнаружения изменений в DOM. С помощью этого API можно наблюдать за добавлением или удалением узлов DOM, за изменением атрибутов элементов, или, например, за изменением текстов текстовых узлов. Зачем это нужно?

Есть немало ситуаций, в которых API MutationObserver может оказаться очень кстати. Например:

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


Текстовый редактор, работающий в браузере

Выше приведены лишь несколько ситуаций, в которых возможности MutationObserver могут оказаться полезными. На самом деле их гораздо больше.

Как пользоваться MutationObserver


Использовать MutationObserver в веб-приложениях довольно просто. Нужно создать экземпляр MutationObserver, передав соответствующему конструктору функцию, которая будет вызываться каждый раз, когда в DOM будут происходить изменения. Первый аргумент функции — это коллекция всех произошедших мутаций в виде единого пакета. Для каждой мутации предоставляется информация о её типе и об изменениях, которые она представляет.

var mutationObserver = new MutationObserver(function(mutations) {
  mutations.forEach(function(mutation) {
    console.log(mutation);
  });
});

У созданного объекта есть три метода:

  • Метод observe запускает процесс отслеживания изменений DOM. Он принимает два аргумента — узел DOM, за которым нужно наблюдать, и объект с параметрами.
  • Метод disconnect останавливает наблюдение за изменениями.
  • Метод takeRecords возвращает текущую очередь экземпляра MutationObserver, после чего очищает её.

Вот как включить наблюдение за изменениями:

// Запускаем наблюдение за изменениями в корневом HTML-элементе страницы
mutationObserver.observe(document.documentElement, {
  attributes: true,
  characterData: true,
  childList: true,
  subtree: true,
  attributeOldValue: true,
  characterDataOldValue: true
});

Теперь предположим, что в DOM имеется простейший элемент div:

<div id="sample-div" class="test"> Simple div </div>

Используя jQuery, можно удалить атрибут class из этого элемента:

$("#sample-div").removeAttr("class");

Благодаря тому, что мы начали наблюдение за изменениями, предварительно вызвав mutationObserver.observe(...), и тому, что функция, реагирующая на поступление нового пакета изменений, выводит полученные данные в консоль, мы увидим в консоли содержимое соответствующего объекта MutationRecord:


Объект MutationRecord

Тут можно видеть мутации, причиной которых стало удаление атрибута class.

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

// Прекратим наблюдение за изменениями
mutationObserver.disconnect();

Поддержка MutationObserver в различных браузерах


API MutationObserver пользуется широкой поддержкой в браузерах:


Поддержка MutationObserver

Альтернативы MutationObserver


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

  • Опрос (polling).
  • Механизм MutationEvents.
  • CSS-анимация.

▍Опрос


Самый простой и незамысловатый способ отслеживания изменений DOM — опрос. Используя метод setInterval можно запланировать периодическое выполнение функции, которая проверяет DOM на предмет изменений. Естественно, использование этого метода значительно снижает производительность веб-приложений.

▍MutationEvents


API MutationEvents было представлено в 2000 году. Несмотря на то, что это API позволяет решать возлагаемые на него задачи, события мутации вызываются после каждого изменения DOM, что, опять же, приводит к проблемам с производительностью. Теперь API MutationEvents признано устаревшим и вскоре современные браузеры перестанут его поддерживать.


Поддержка MutationEvents

▍CSS-анимация


На самом деле, альтернатива MutationObserver в виде CSS-анимаций может показаться несколько странной. Причём тут анимация? В целом, идея тут заключается в создании анимации, которая будет вызвана после того, как элемент будет добавлен в DOM. В момент запуска анимации будет вызвано событие animationstart. Если назначить обработчик для этого события, можно узнать точное время добавления нового элемента в DOM. Время выполнения анимации при этом должно быть настолько маленьким, чтобы она была практически незаметна для пользователя.

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

<div id="container-element"></div>

Для организации наблюдения за добавлением в него узлов нужно настроить последовательность ключевых кадров CSS-анимации, которые запустятся при добавлении узла:

@keyframes nodeInserted { 
 from { opacity: 0.99; }
 to { opacity: 1; } 
}

После создания ключевых кадров анимация должна быть применена к элементам, за которыми нужно наблюдать. Обратите внимание на длительность анимации. Она очень мала, благодаря чему анимация оказывается практически незаметной.

#container-element * {
 animation-duration: 0.001s;
 animation-name: nodeInserted;
}

Тут мы добавляем анимацию ко всем узлам-потомкам элемента container-element. Когда анимация заканчивается, вызывается соответствующее событие.

Теперь нужна JS-функция, которая будет играть роль обработчика событий. Внутри функции, в первую очередь, необходимо выполнить проверку event.animationName для того, чтобы убедиться, что это — именно та анимация, которая нас интересует.

var insertionListener = function(event) {
  // Убедимся в том, что это именно та анимация, которая нас интересует
  if (event.animationName === "nodeInserted") {
    console.log("Node has been inserted: " + event.target);
  }
}

Теперь добавим обработчик события к родительскому элементу. В разных браузерах это делается по-разному:

document.addEventListener("animationstart", insertionListener, false); // standard + firefox
document.addEventListener("MSAnimationStart", insertionListener, false); // IE
document.addEventListener("webkitAnimationStart", insertionListener, false); // Chrome + Safari

Вот как обстоит дело с поддержкой CSS-анимации в различных браузерах.


Поддержка CSS-анимации в различных браузерах

Итоги


Мы рассмотрели API MutationObserver и альтернативные способы наблюдения за изменениями DOM. Надо отметить, что MutationObserver имеет множество преимуществ перед этими альтернативами. В целом, можно говорить о том, что это API способно сообщать о любых изменениях, которые могут возникать в DOM, о том, что оно хорошо оптимизировано, давая информацию об изменениях, собранную в пакеты. Кроме того, API MutationObserver пользуется поддержкой всех основных современных браузеров, существуют и полифиллы для него, основанные на MutationEvents.

Автор материала отмечает, что MutationObserver занимает центральное место в библиотеке SessionStack, которая направлена на организацию сбора данных о том, что происходит с веб-страницами.

Предыдущие части цикла статей:

Часть 1: Как работает JS: обзор движка, механизмов времени выполнения, стека вызовов
Часть 2: Как работает JS: о внутреннем устройстве V8 и оптимизации кода
Часть 3: Как работает JS: управление памятью, четыре вида утечек памяти и борьба с ними
Часть 4: Как работает JS: цикл событий, асинхронность и пять способов улучшения кода с помощью async / await
Часть 5: Как работает JS: WebSocket и HTTP/2+SSE. Что выбрать?
Часть 6: Как работает JS: особенности и сфера применения WebAssembly
Часть 7: Как работает JS: веб-воркеры и пять сценариев их использования
Часть 8: Как работает JS: сервис-воркеры
Часть 9: Как работает JS: веб push-уведомления

Уважаемые читатели! Пользуетесь ли вы MutationObserver в своих проектах?

Tags:
Hubs:
Total votes 24: ↑22 and ↓2+20
Comments7

Articles

Information

Website
ruvds.com
Registered
Founded
Employees
11–30 employees
Location
Россия
Representative
ruvds