Использование MutationObserver для определения момента добавления узла в документ - PullRequest
0 голосов
/ 21 сентября 2019

Я хотел бы определить, когда в документ добавляется определенный DOMNode из контекста создания этого DOMNode.

Вот что у меня есть:

function getThingThatFormatsItself() {
    const MY_NODE = createNode();

    const observer = new MutationObserver(function (records) {
        records.forEach(record => {
            record.addedNodes.forEach(n => {
                if (n === MY_NODE) {
                    observer.disconnect();
                    doFormatting();
                }
            });
        })
    });
    observer.observe(document, {childList: true, subtree: true});

    // do formatting stuff that relies on the element being in DOM
    function doFormatting() {
        console.log(`It's been added.`);
    }

    return MY_NODE;
}

/* ELSEWHERE IN MY CODE */

// Now that Thing is added to DOM, it can format itself.
// However, this doesn't work unless it's added via setTimeout!
// Also, in the meantime, we are needlessly iterating through every single node added to the document.
$("#Foo").append(getThingThatFormatsItself());    

Две проблемы с этим:

  1. Это не сработает, если только нет setTimeout до добавления thingNode к документу.Похоже, .observe() не вступает в силу немедленно.Это правда?
  2. Абсолютно абсурдно повторять каждый узел, добавленный в документ, между моментом создания и добавления вещи в документ.

Есть лиспособ увидеть, когда мой узел добавляется без необходимости полагаться на внешних вызывающих абонентов, используя setTimeout и без необходимости перебирать каждый добавленный узел за это время?

Это действительно "сбивает с толку", еслиэто хорошо, что я не могу наблюдать сам фактический узел для добавления и удаления - только его дочерние узлы.Какой-то дизайн.Также довольно «запутанно» то, что .observe(), похоже, помещается в очередь событий, а не выполняется немедленно.

1 Ответ

1 голос
/ 21 сентября 2019
  1. Обратный вызов 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 для постановки в очередь некоторого зависимого кода в очереди микрозадач.Для старых браузеров прямо в статье есть простой полифилл.

  2. Проверьте 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.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...