使用MutationObserver检测何时将节点添加到文档 [英] Using MutationObserver to detect when a Node is added to document

查看:40
本文介绍了使用MutationObserver检测何时将节点添加到文档的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想从创建DOMNode的上下文中检测何时将特定的DOMNode添加到文档中.

I'd like to detect when a specific DOMNode is added to the document, from within the context of the creation of that DOMNode.

这是我到目前为止所拥有的:

Here's what I have so far:

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. 除非在将thingNode添加到文档之前存在 setTimeout ,否则这将无法工作.看来 .observe()不会立即生效.这是真的吗?
  2. 在创建事物并将其添加到文档的时间之间,必须遍历添加到文档的每个节点是绝对荒谬的.
  1. This doesn't work unless there's a setTimeout before adding thingNode to the document. It appears .observe() doesn't take effect immediately. Is this true?
  2. It's absolutely absurd to have to iterate through every node added to the document in between the time Thing is created and added to document.

是否可以查看添加节点的时间,而不必依靠使用setTimeout的外部调用者,也不必在此期间迭代每个添加的节点?

很好地说,我不能观察到实际的节点本身可以​​添加和删除,仅观察其子节点,这真是令人困惑".一些设计.将 .observe()似乎放在事件队列中而不是立即执行也很令人困惑".

It's really "confusing", to put it nicely, that I can't observe the actual node itself for addition and removal -- only its children nodes. Some design. It's also quite "confusing" that .observe() seems to be put on the event queue rather than executed immediately.

推荐答案

  1. MutationObserver回调在微任务队列处理阶段的事件循环周期结束时运行,该阶段发生在主代码阶段完成后的 ,这就是为什么在当前当前阶段之后调用doFormatting()的原因运行代码完成(可以说整个函数调用堆栈).

  1. MutationObserver callback runs at the end of an event loop cycle during the microtask queue processing phase which occurs after the main code phase has completed which is why doFormatting() is called after the currently running code completes (the entire function call stack so to speak).

除非您的其他代码中有其他原因使对doFormatting的假设在当前事件循环中被调用,或者取决于DOM布局被更新,否则它应与setTimeout大致相同,后者将安排回调在下一次运行事件循环周期.

Unless there's something else in your other code that makes assumptions on doFormatting being called in the current event loop or depends on DOM layout being updated it should be more or less the same as using setTimeout which schedules the callback to run in the next event loop cycle.

与不推荐使用的同步DOM突变事件相比,MutationObserver会累积大量的突变并将其全部报告在微任务队列中的原因是,它提供了更快的观察能力.

The reason MutationObserver accumulates batches of mutations and reports them all in the microtask queue is to provide a much faster observation capability compared to the deprecated synchronous DOM Mutation Events.

解决方案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:

Solution 2: don't use MutationObserver, instead intercept 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将一些相关代码放入微任务队列中.对于较旧的浏览器,本文中有一个简单的polyfill.

Other things to try: window.queueMicrotask(callback) instead of setTimeout to enqueue some of the dependent code in the microtask queue. For older browsers there's a simple polyfill right there in the article.

检查 document.contains(MY_NODE)(如果在ShadowDOM内则无济于事)或 MY_NODE.parentNode 而不是枚举突变:

Check document.contains(MY_NODE) (won't help if inside ShadowDOM) or MY_NODE.parentNode instead of enumerating the mutations:

new MutationObserver((mutations, observer) => {
  if (MY_NODE.parentNode) {
    observer.disconnect();
    doFormatting();
  }
}).observe(document, {childList: true, subtree: true});

这也更加可靠,因为在通常情况下,该节点可能是另一个已添加节点的子节点,而不是作为addNodes数组中的单独项.

This is also more reliable because in a general case the node may be a child of another added node, not as as a separate item in the addedNodes array.

这篇关于使用MutationObserver检测何时将节点添加到文档的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆