Ответ 1
Документы по MDN имеют общий неполный пример и не показывают типичных ошибок. Библиотека мутаций предоставляет удобную оболочку, но, как и все оболочки, она добавляет накладные расходы. См. Производительность MutationObserver для обнаружения узлов во всем DOM.
Создайте и запустите обозреватель.
Давайте использовать рекурсивный MutationObserver для всего документа, который сообщает обо всех добавленных/удаленных узлах.
var observer = new MutationObserver(onMutation);
observer.observe(document, {
childList: true, // report added/removed nodes
subtree: true, // observe any descendant elements
});
Наивное перечисление добавленных узлов.
Замедляет загрузку чрезвычайно больших/сложных страниц, см. Производительность.
Иногда пропускаются элементы H1, объединенные в родительский контейнер, см. следующий раздел.
function onMutation(mutations) {
mutations.forEach(mutation, m => {
[...m.addedNodes]
.filter(node =>
node.localName === 'h1' && /foo/.test(node.textContent))
.forEach(h1 => {
h1.innerHTML = h1.innerHTML.replace(/foo/, 'bar');
});
});
}
Эффективное перечисление добавленных узлов.
Теперь самая сложная часть. Узлы в записи мутации могут быть контейнерами во время загрузки страницы (например, весь блок заголовка сайта, в котором все его элементы представлены как один добавленный узел): спецификация не требует перечисления каждого добавленного узла. индивидуально, поэтому нам придется заглядывать внутрь каждого элемента, используя querySelectorAll
(очень медленно) или getElementsByTagName
(очень быстро).
function onMutation(mutations) {
for (var i = 0, len = mutations.length; i < len; i++) {
var added = mutations[i].addedNodes;
for (var j = 0, node; (node = added[j]); j++) {
if (node.localName === 'h1') {
if (/foo/.test(node.textContent)) {
replaceText(node);
}
} else if (node.firstElementChild) {
for (const h1 of node.getElementsByTagName('h1')) {
if (/foo/.test(h1.textContent)) {
replaceText(h1);
}
}
}
}
}
}
function replaceText(el) {
const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
for (let node; (node = walker.nextNode());) {
const text = node.nodeValue;
const newText = text.replace(/foo/, 'bar');
if (text !== newText) {
node.nodeValue = newText;
}
}
}
Почему две уродливые ванильные циклы for
? Поскольку forEach
и filter
и ES2015 for (val of array)
могут работать очень медленно в некоторых браузерах, см. Производительность MutationObserver для обнаружения узлов во всем DOM.
Почему TreeWalker? Для сохранения любых слушателей событий, прикрепленных к подэлементам. Чтобы изменить только узлы Text
: у них нет дочерних узлов, и их изменение не вызывает новую мутацию, потому что мы использовали childList: true
, а не characterData: true
.
Обработка относительно редких элементов с помощью живой коллекции HTMLC без перечисления мутаций.
Поэтому мы ищем элемент, который предполагается использовать редко, например, тег H1, или IFRAME и т.д. В этом случае мы можем упростить и ускорить обратный вызов наблюдателя с помощью автоматически обновляемого HTMLCollection, возвращаемого getElementsByTagName.
const h1s = document.getElementsByTagName('h1');
function onMutation(mutations) {
if (mutations.length === 1) {
// optimize the most frequent scenario: one element is added/removed
const added = mutations[0].addedNodes[0];
if (!added || (added.localName !== 'h1' && !added.firstElementChild)) {
// so nothing was added or non-H1 with no child elements
return;
}
}
// H1 is supposed to be used rarely so there'll be just a few elements
for (var i = 0, h1; (h1 = h1s[i]); i++) {
if (/foo/.test(h1.textContent)) {
// reusing replaceText from the above fragment of code
replaceText(h1);
}
}
}