如何有效地响应从 DOM 添加或删除的单个节点?

问题描述

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

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

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

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


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

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

这些方法都不是有效的。

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

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

解决方法

我首先尝试钩住元素的所有函数和属性,以查看添加元素时是否发生火灾,但没有成功。

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

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

它为元素添加了一个隐藏的图像,只有当它被添加到主体的 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);


        
    
});

,

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

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());
  }
}