【问题标题】:How can I efficiently respond to a single node being added or removed from the DOM?如何有效地响应从 DOM 中添加或删除的单个节点?
【发布时间】:2021-01-05 23:00:01
【问题描述】:

我有一个正在添加到 DOM 的节点是某个未知的地方。我不知道父母会是什么,我唯一能做的假设是它会在document.body的子树中。

此外,我不能假设节点将作为自身添加;当它进入 DOM 时,它可能会隐藏在某个其他元素的子树中。

我希望在元素从 DOM 中删除和添加时发生回调。

我尝试使用Mutation Observer,但它不适合这项工作。 Mutation Observers 不能观察特定元素,而是观察一个元素的子元素。鉴于我不知道父元素是什么,我无法观察父元素是否添加了这个特定的子元素。


到目前为止,我能找到的唯一解决方案是在 整个 DOM 上使用突变观察者,从 document.body 开始,带有子树,然后遍历每个子树的整个子树添加节点搜索我正在寻找的一个节点。

我拥有的下一个最佳解决方案是在任何时候添加或删除 anything 时检查我试图观察的每个节点是否在页面上。这个最大的问题是它需要持有对可能被弃用的 HTMLELements 的引用,并且最终会阻塞垃圾收集器。

这些方法都不是有效的。

当然,一定有更好的方法吗?这不会是一个那么难的问题吧?

function onElementAdd(node, cb);
function onElementRemove(node, cb);

【问题讨论】:

  • 哇。显然我 4 年前问过同样的问题:stackoverflow.com/questions/40364156/…
  • Mutationobservers 看起来正是你所要求的,因为你观察了 document.body 的子树。你收到的突变记录不是包含所有必需的信息吗?
  • 您必须遍历每个添加元素的整个子树来搜索一个。效率低得离谱。
  • 不,你有 MutationRecord.addedNodes/removedNodes,见developer.mozilla.org/en-US/docs/Web/API/MutationRecord
  • 您可以扩展一个通用的div,它可以让您访问这些回调。了解这些回调的来源很有趣,因为您甚至可以在其中执行 super.connectedCallback()。我想这些只有在扩展时才会暴露。

标签: javascript mutation-observers


【解决方案1】:

我首先尝试hook元素的所有函数和属性,看看添加元素时是否有火,但没有运气。

然后我尝试了代理和 MutationObserver 也没有运气。

现在这个解决方案使用了一些我喜欢的超级 hacky 解决方案。

它向元素添加一个隐藏的图像,只有当它被添加到 body 的 dom 时才会触发回调。那是添加的回调。一旦它显示出来,它就会向父节点添加一个观察者,并在元素不再具有父节点时触发删除回调。根据您的需要进行调整。

function addLazyImage(el)
{
    let img = document.createElement("img");

    img.setAttribute("loading", "lazy");

    img.width = 0;
    img.height = 0;

    img.src = "https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_92x30dp.png";
    
    el.appendChild(img);

    return img;
}


function monitorElement(el, added, removed)
{
    let img = addLazyImage(el);

    img.onload = function()
    {
        // The element has been added to the visible dom

        const observer = new MutationObserver(function(e)
        {
            console.log("event happened");
    
            // If the element no longer has a parent, assume its been removed
            if(el.parentNode == null)
            {
                // The lazy loading only happens once, recreate the image element
                // every time its used
                let onload = img.onload;

                img = addLazyImage(el);

                img.onload = onload;

                removed();
            }
        });
    
        observer.observe(el.parentNode, {subtree: true, childList: true});
        
        added();
    }
}



$(document).ready(function()
{
    let el = document.createElement("div");

    el.innerHTML = "you wot";

    monitorElement(el, () =>
    {
        console.log("Im in the dom");
    }, () =>
    {
        console.log("Im not in the dom");
    });

    setTimeout(function()
    {
        console.log("appending element to body");

        document.body.appendChild(el);
        
    }, 1000);

    setTimeout(function()
    {
        console.log("Removing from the body");

        document.body.removeChild(el);

    }, 2000);

    setTimeout(function()
    {
        console.log("appending element to another element");

        document.querySelector("#div-container").appendChild(el);

    }, 3000);


        
    
});

【讨论】:

  • 您甚至不必发出网络请求。而不是onload 监听onerror 并将src 设置为空字符串。
  • @Kaiido 不错的快捷方式
  • 对于大多数项目来说,这是一个很酷的解决方案。这里的一个缺点是任何被监视的元素都不能被垃圾收集。在变异观察者中总会有对它们的引用。此外,那些突变观察者堆积起来。并且移动我认为可能会破坏移除突变观察者的元素。
  • @SephReed 当元素从 DOM 中移除时,您只需调用 observer.disconnect() 即可再次收集 el。顺便说一句,约翰,这里的另一个问题,不要看el.parentNode 来告诉您该元素是否仍在 DOM 中,他们可能已经删除了祖父母并且该检查将失败。相反,如果我们想在 DOM 中全局了解,则有一个 Node.isConnected 属性,或者 OP 可能想要更多类似 document.body.contains(el) 的东西
【解决方案2】:

效率不高,但这是我所拥有的最好的:

const div = document.createElement("div");
div.textContent = "Hello";

observeNodeAddRemove(div, (added) => {
  console.log(`div added: ${added}`);
});

setTimeout(() => document.body.append(div), 50);
setTimeout(() => div.remove(), 500);



//////-------------------------

function observeNodeAddRemove(domNode, cb) {
  assertMutationObserver();
  domNode._addRemoveCb = cb;
}


// group together any mutations which happen within the same 10ms
var mutationsBuffer = [];  //: MutationRecord[]
var bufferTimeout;
var mutationObserver; //: MutationObserver
function assertMutationObserver() {
  if (mutationObserver) { return; }
  if (typeof MutationObserver === "undefined") { return; } // Node JS testing
  if (typeof window !== "undefined") { return; } // Node JS testing
  mutationObserver = new MutationObserver((mutationsList) => {
    mutationsList.forEach((mutation) => {
      if (mutation.type !== 'childList') { return; }
      mutationsBuffer.push(mutation);
    });
    
    // timeout/buffer stops thrashing from happening
    if (bufferTimeout) { clearTimeout(bufferTimeout); }
    bufferTimeout = setTimeout(() => {
      bufferTimeout = undefined;
      const oldBuffer = mutationsBuffer;
      mutationsBuffer = [];

      // every node that's been added or removed
      const allNodes = new Map(); //<Node, void>
      for (const mutation of oldBuffer) {
        mutation.removedNodes.forEach((node) => allNodes.set(node));
        mutation.addedNodes.forEach((node) => allNodes.set(node));
      }

      // function for traversing sub tree
      // (root: Node, cb: (node: Node) => any) => {
      const permeate = (root, cb) => {
        cb(root);
        root.childNodes.forEach((child) => permeate(child, cb));
      }

      
      const nodeList = Array.from(allNodes.keys());
      nodeList.forEach((root) => permeate(root, (child) => {
        if (child._addRemoveCb) {
          const onPage = child.getRootNode() === document;
          child._addRemoveCb(onPage);
        }
      }));
    }, 10);
  });

  var config = { childList: true, subtree: true };
  const tryAddObserver = () => mutationObserver.observe(document.body, config);
  if (document && document.body) { 
    tryAddObserver(); 
  } else {
    document.addEventListener("DOMContentLoaded", () => tryAddObserver());
  }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-09
    • 2015-03-26
    • 2023-03-08
    • 1970-01-01
    • 2013-06-04
    相关资源
    最近更新 更多