Мониторинг завершения загрузки и изменений DOM — приложение MutationObserver

JavaScript
Мониторинг завершения загрузки и изменений DOM — приложение MutationObserver

упоминалось в предыдущем разделеЯвляется ли операция DOM «синхронной» или «асинхронной», как точно отслеживать успешность рендеринга DOM — MutationObserver.

1. Что такое MutationObserver

Интерфейс предоставляет возможность отслеживать изменения, внесенные в дерево DOM. Он был разработан как замена старой функциональности MutationEvents, которая была частью спецификации DOM3 Events.

Источник: МДН

Просто и грубо, это следить за изменениями дерева DOM.

Затем замененныйMutationEventsчто это такое?

2. События Мутации

  1. Сначала уточните:MutationEventsЭто также было написано в MDN, это былоDOM EventПризнавая ошибку в API,объект для использования.
  2. Суть недостатка заключается в двух моментах:Кроссбраузерная поддержкаа такжепроблемы с производительностью
  3. MutationEventsПринцип:Слушайте DOM, связывая событияНа первый взгляд все кажется нормальным, поэтому давайте перечислим связанные отслеживаемые события:
DOMAttributeNameChanged
DOMCharacterDataModified
DOMElementNameChanged
DOMNodeInserted
DOMNodeInsertedIntoDocument
DOMNodeRemoved
DOMNodeRemovedFromDocument
DOMSubtreeModified
  1. Не помните, при таком количестве событий браузеры разных ядер и версий боятся, что совместимость будет вне времени.
  2. В частности, говоря о проблемах с производительностью (выделено мной):
    • (1) Есть много событий, которые видны.Если вы слушаете несколько элементов, вы привязываете несколько элементов.
    • (2) Пока это обязывающее событие, оно неотделимопузырьилизахватыватьВо-вторых, и смысл мониторинга может быть реакцией на большое количество частых изменений. Кроме того, что, если вы хотите отслеживать несколько элементов? Что делать, если существует несколько уровней вложенности, и каждый слой необходимо контролировать? Что, если отец, сын, брат и родовая семья столкнутся вместе? (Записать его и попытаться сохранить?)
    • (3) Немедленное выполнение события привязки может прервать оставшиеся изменения DOM, то есть событие запускается до перемещения события.

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

  1. Продолжая, эти вопросы на самом деле используют观察者模式можно сделать просто отлично, так что смотриMutationObserverЗнай имя.

Три, одно предложение, чтобы объяснить режим наблюдателя

Поскольку эта статья в основном знакомитMutationObserver, так какMutationObserverПринцип проектирования, простое понимание заключается в том, что

Если A хочет прочитать новости, A сначала «оплатит (подписку)» B, затем B отправит газету A, когда появятся новости, и A выберет новости, которые он хочет прочитать.

  • A - Подписчик - ПройтиMutationObserverполучить возвращаемое значение (получить газету) - может быть бесконечное количество A, чтобы увидеть B
  • Б - Наблюдается -DOM- Отправлять новый контент при смене (новая газета) - B решает A

Конечно, более подробные шаблоны наблюдателей будут доступны в ближайшем будущем.

В-четвертых, улучшение MutationObserver

для вышеперечисленногоMutationEventsнедостатки, давайте поговорим оMutationObserverПреимущества.

  1. Проблемы совместимости браузера:
const mutationObserver = new MutationObserver(callback);

MutationObserverЭто конструктор, а совместимость низкая (поддерживается только IE11 и выше), стоит отметить, что совместимость с мобильным терминалом лучше.

  1. Несколько инцидентов:
const mutationObserver = new MutationObserver((mutations, observer) => {
	console.log(observer); // 观察者实例
	console.log(mutations); // 变动数组
	mutations.forEach(function(mutation) {
	    console.log(mutation);
	});
});

callbackВ качестве события слушателя возвращает два фиксированных параметра.mutationsа такжеobserver.mutations- изменить массивobserver- Экземпляр наблюдателя

Конкретная функция, которую нужно выполнить, посмотрите вниз

  1. Немедленные проблемы с триггером/множеством триггеров:
function editContent() {
    const content = document.getElementById('content');
    console.log(1);
    // --------------------------
    observer(); // 订阅
    // --------------------------
    content.style.background = 'lightblue';
    content.style.background = 'red';
    console.log(2);
    content.innerHTML = 4433;
    console.log(3);
    const newNode = document.createElement('p');
    newNode.innerHTML = 888888;
    content.appendChild(newNode);
    console.log(4);
}

Результаты:

// 1
// 2
// 3
// 4
// MutationObserver {}
// (4)[MutationRecord, MutationRecord, MutationRecord, MutationRecord]
  • Асинхронное выполнение, вставленное в очередь микрозадач, и скрипт будет выполнен после выполнения скрипта. (Посты, в которых микрозадачи не могли быть яснее)
  • mutationsпараметры будут контролироватьсяDOMВсе записи об изменениях для执行顺序упакован в数组вернуть.
  • Вы можете контролировать цель через элементы конфигурацииDOMВниз子元素журнал изменений

Пять, основное использование MutationObserver

Мы видели выше, как пройтиMutationObserverКонструктор создает экземпляр объекта. Следующим шагом будет привязка被观察者, и какие изменения необходимо наблюдать.

1. MutationObserver.observe(dom, options)

Запустите прослушиватель и получите два параметра.

  • Первый параметр: наблюдаемыйDOMузел
  • Второй параметр: настройте параметры изменений, которые необходимо соблюдать (помнитеMutationEventsТам много событий, тут делается через пункты конфигурации)
mutationObserver.observe(content, {
    attributes: true, // Boolean - 观察目标属性的改变
    characterData: true, // Boolean - 观察目标数据的改变(改变前的数据/值)
    childList: true, // Boolean - 观察目标子节点的变化,比如添加或者删除目标子节点,不包括修改子节点以及子节点后代的变化
    subtree: true, // Boolean - 目标以及目标的后代改变都会观察
    attributeOldValue: true, // Boolean - 表示需要记录改变前的目标属性值
    characterDataOldValue: true, // Boolean - 设置了characterDataOldValue可以省略characterData设置
    // attributeFilter: ['src', 'class'] // Array - 观察指定属性
});

Примечание:

  1. attributeFilter/attributeOldValueприоритет надattributes
  2. characterDataOldValueприоритет надcharacterData
  3. attributes/characterData/childList(или более конкретный) по крайней мере один изtrue;
  4. Определенный элемент существует, соответствующий параметр может быть忽略или должно бытьtrue

Прикрепил:Разработать исходный текст API

2. MutationObserver.disconnect()

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

3. MutationObserver.takeRecords()

Очистить историю изменений. То есть необработанные изменения больше не обрабатываются. Этот метод возвращает массив записей об изменениях, обратите внимание, что этот методВступает в силу немедленно.

Вложение: takeRecords меняет содержимое поля записиMutationRecordобъект

/*
MutationRecord = {
  type:如果是属性变化,返回"attributes",如果是一个CharacterData节点(Text节点、Comment节点)变化,返回"characterData",节点树变化返回"childList"
  target:返回影响改变的节点
  addedNodes:返回添加的节点列表
  removedNodes:返回删除的节点列表
  previousSibling:返回分别添加或删除的节点的上一个兄弟节点,否则返回null
  nextSibling:返回分别添加或删除的节点的下一个兄弟节点,否则返回null
  attributeName:返回已更改属性的本地名称,否则返回null
  attributeNamespace:返回已更改属性的名称空间,否则返回null
  oldValue:返回值取决于type。对于"attributes",它是更改之前的属性的值。对于"characterData",它是改变之前节点的数据。对于"childList",它是null
}
*/

6. Расширенное применение MutationObserver

  • Создан JS-скрипт слушателяDOM渲染完成
  • Мониторинг изменений и обработки содержимого изображений/текстового редактора/узлов
  • оvueдляMutationObserverПриложения
1. Слушайте завершение рендеринга DOM, созданного скриптом JS.

Как упоминалось ранее,DOM渲染встретить脚本阻塞Возникает ситуация, аналогичная «асинхронной», влияющая на последующие операции с DOM. Хотя эту проблему можно решить, активировав перекомпоновку, это не очень хороший выбор в сложных бизнес-сценариях/сценариях с избыточными данными. теперь, когдаMutationObserverв состоянии контролироватьDOM树середина子节点изменения, то используя это, вы можете отслеживатьdocumentили父节点Изменения дерева DOM.

Маленькие каштаны:

// html
<div id="content">66666</div>

// js
let time = 4;
let arr = new Array(time);
let content = document.getElementById('content');
let mutationObserver = new MutationObserver(obsCallback); // 创建实例
obs(); // 绑定被观察者
obstruct(); // 执行阻塞

// 完成创建
function obsCallback(mutations, observer) {
    console.log(`创建完成!`);
    console.log(observer); // 观察者实例
    console.log(mutations); // 变动数组
}

function obs() {
    mutationObserver.observe(content, {
	    childList: true,
	    subtree: true,
    });
}

function obstruct() {
  for (let i = 0; i < arr.length; i++) {
		arr[i] = `<div>${i}</div>`;
	}

	arr.map((item, idx) => {
		for(let i = 0; i < 3000; i++) console.log(1)
		content.innerHTML += item;
	});
}

2. Отслеживайте изменения и обработку содержимого изображений/форматированного текстового редактора/узлов.

Раньше былcontenteditableатрибут, делатьDOMРедактируемый, сделать форматированный текстовый редактор и другие приложения. Для таких приложений можно легко реализовать ряд операций, таких как фильтрация ключевых слов или содержимого, предотвращение редактирования (восстановление содержимого) и неудаляемые водяные знаки изображения (Прикрепленный 1)

Скромный каштан, который мешает редактированию:

// html
<div id="content">66666</div>

// js
function obsCallback(mutations, observer) {
    console.log(observer); // 观察者实例
    console.log(mutations); // 变动数组
    mutations.forEach(mutation => {
        if (mutation.target.contentEditable === 'true') {
            mutation.target.setAttribute('contenteditable', 'false');
        }
    })
}

function obs() {
    mutationObserver.observe(content, {
        // attributes: true,
        attributeFilter: ['contenteditable']
        // characterData: true,
        // childList: true,
        // subtree: true,
    });
}

Прикрепил:Реализовать неудаляемый водяной знак

3. ОvueдляMutationObserverПриложения
  • vueФреймворк до vue2.0, дляMutationObserverприложениеnextTick; Принцип заключается в использованииMutationObserverАсинхронные функции обратного вызова ставятся в очередь в очереди микрозадач. Конкретная операция состоит в том, чтобы создать новый узел и наблюдать за ним, а также обновлять его содержимое по желанию.

  • Какой? Почему раньше 2.0, сейчас используетсяMessageChannel,чтоMessageChannel? Это следующая тема.

  • Зачем использоватьMutationObserver, или это иPromiseа такжеsetTimeoutгде разница.vueприоритетPromise,MutationObserver,setTimeout. когдаPromiseВыберите, если несовместимоMutationObserverС точки зрения как функциональности, так и производительности они в основном одинаковы, просто добиться этого немного проблематично, чтобы создать новый узел, просто пошевелите мышцей.setTimeoutНаконец, для совместимости с альтернативными вариантами использования причины следующие.

  • причина:MutationObserverа такжеPromiseмикрозадачи,setTimeoutпринадлежит задаче макроса; В механизме выполнения браузера повторный рендеринг выполняется каждый раз при выполнении задачи макроса, а микрозадача выполняется в текущей задаче макроса, которая может получить последнее обновление как можно быстрее.Если есть соответствующая операция DOM ( вспомнить предыдущую статью) , которая будет выполнена в конце задачи макроса. но если вы используетеsetTimeoutДля задач макросов обновление содержимого должно дождаться выполнения всех задач макросов в начале очереди, и если в содержимом обновления есть операция DOM, браузер отобразит ее дважды.

  • 被弃用的原因: 一个兼容性BUG。 дляiOS UIWebView, страница будет прервана после запуска в течение определенного периода времени, текущая нативнаяMutationObserverНет хорошего решения, еслиIOS10 SafariОтдельно от других операционных сред это несколько лишнее. (Просто измените на более совместимый)оригинальный ответ


Использованная литература:

  1. developer.Mozilla.org/en-US/docs/…
  2. сегмент fault.com/ah/119000001…
  3. JavaScript.Ruan Yifeng.com/Dom/Деревянная башня Body Oh…
  4. docs.microsoft.com/en-US/pre vi…