Обратный вызов MutationObserver выполняется в конце цикла цикла событий во время фазы обработки очереди микрозадач, которая происходит после фазы основного кода, поэтому doFormatting () вызывается послетекущий выполняемый код завершается (так сказать, весь стек вызовов функций).
Если в другом вашем коде нет чего-то еще, что делает предположения о вызове doFormatting в текущем цикле событий или зависит от того, обновляется ли его макет DOMдолжно быть более или менее таким же, как при использовании setTimeout, который планирует обратный вызов для запуска в следующем цикле цикла обработки событий.
Причина, по которой MutationObserver накапливает пакеты мутаций и сообщает о них всем в очереди для микрозадач, состоит в том, чтобы обеспечить намного более быструювозможность наблюдения по сравнению с устаревшими синхронными событиями мутации DOM.
Решение 1: использовать обратные вызовы для запуска кода после doFormatting ()
function onNodeAdopted(node, callback) {
new MutationObserver((mutations, observer) => {
if (node.parentNode) {
observer.disconnect();
callback(node);
}
}).observe(document, {childList: true, subtree: true});
return node;
}
function getThingThatFormatsItself(callback) {
return onNodeAdopted(createNode(), node => {
doFormatting(node);
console.log('Formatted');
callback(node);
});
}
$("#Foo").append(getThingThatFormatsItself(node => {
console.log('This runs after doFormatting()');
doMoreThings();
}));
console.log('This runs BEFORE doFormatting() as MutationObserver is asynchronous')
Решение 2:не используйте MutationObserver ,вместо этого перехватите Node.prototype.appendChild:
const formatOnAppend = (() => {
const hooks = new Map();
let appendChild;
function appendChildHook(node) {
appendChild.call(this, node);
const fn = hooks.get(node);
if (fn) {
hooks.delete(node);
// only restore if no one chained later
if (!hooks.size && Node.prototype.appendChild === appendChildHook) {
Node.prototype.appendChild = appendChild;
}
fn(node);
}
return node;
}
return {
register(node, callback) {
if (!hooks.size) {
appendChild = Node.prototype.appendChild;
Node.prototype.appendChild = appendChildHook;
}
hooks.set(node, callback);
return node;
},
}
})();
Использование:
function getThingThatFormatsItself() {
return formatOnAppend.register(createNode(), node => {
console.log('%o is added', node);
});
}
Другие действия: window.queueMicrotask (обратный вызов) вместо setTimeout для постановки в очередь некоторого зависимого кода в очереди микрозадач.Для старых браузеров прямо в статье есть простой полифилл.
Проверьте document.contains(MY_NODE)
(не поможет, если внутри ShadowDOM) или MY_NODE.parentNode
вместо перечисления мутаций:
new MutationObserver((mutations, observer) => {
if (MY_NODE.parentNode) {
observer.disconnect();
doFormatting();
}
}).observe(document, {childList: true, subtree: true});
Это также более надежно, поскольку в общем случае узел может быть дочерним по отношению к другому добавленному узлу, а не как отдельный элемент в массиве addNodes.