【问题标题】:MutationObserver for when parent changesMutationObserver 用于父级更改时
【发布时间】:2023-03-14 00:30:01
【问题描述】:

有没有办法使用MutationObserver 检测元素的父元素何时更改(即从null 更改为!null - 即元素最初添加到DOM 时)?我找不到任何说明如何实现这一点的文档。

我正在使用document.createElement() 以编程方式创建元素。我从函数返回创建的元素,但想从 within 函数创建一个侦听器,以便在元素最终添加到 DOM 时做出反应,而不知道 where 或 将添加到哪个父级。


老实说,我不太清楚该怎么表达。

const elem = document.createElement('div');

let added = false;
elem.addEventListener('added-to-dom', () => { added = true; });
// ^ how do I achieve this?

assert(added == false);
document.body.addChild(elem);
assert(added == true);

我不明白理解这一点或关闭它的原因有什么困难。

【问题讨论】:

  • null!null 是什么意思?如果元素存在于 DOM 中,那么它总是有父元素,所以你必须精确你的问题。但是使用MutationObserver 基本上你可以跟踪任何你想观察body 元素的东西。
  • 不,MutationObserver 只能观察记录的内容:子节点和属性。您想要的东西 - 检测元素何时附加到实时 DOM - 可以通过已弃用的 DOM 突变事件 AFAIK 来实现。
  • @wOxxOm 添加了说明。
  • 根据您的说明,您的问题不是:我如何检测何时将编程创建的元素添加到 DOM 中
  • 是的,这是一种说法,但我担心我会得到像你这样建议听父母的答案 - 当我不知道父母可能是什么时。我想看看这是否可以通过MutationObservers 来完成,其中的答案听起来像是不能。我真的不明白它是如何令人困惑的。

标签: javascript html mutation-observers


【解决方案1】:

你可以监听DOMNodeInserted-事件并比较元素的id。

注意: 此事件被标记为 Deprericated,并且可能会在不久后的某个时间停止在现代现代浏览器中运行 未来。

let container = document.getElementById('container');
let button = document.getElementById('button');

document.body.addEventListener('DOMNodeInserted', function(event) {
  if (event.originalTarget.id == button.id) {
    console.log('Parent changed to: ' + event.originalTarget.parentElement.id);
  }
});

button.addEventListener('click', function(event) {
  container.appendChild(button);
});
#container {
  width: 140px;
  height: 24px;
  margin: 10px;
  border: 2px dashed #c0a;
}
<div id="container"></div>
<button id="button">append to container</button>

【讨论】:

  • 是的,我担心这是唯一的方法:/ 遗憾的是,他们在没有任何替代“正确”方式的情况下弃用了某些东西。
【解决方案2】:

一种简单但不优雅的方法是猴子补丁Node.prototype.appendChild(以及,如果需要,Element.prototype.appendElement.prototype.insertAdjacentElementNode.prototype.insertBefore)来监视元素何时添加到 DOM:

const elementsToWatch = new Set();
const { appendChild } = Node.prototype;
Node.prototype.appendChild = function(childToAppend) {
  if (elementsToWatch.has(childToAppend)) {
    console.log('Watched child appended!');
    elementsToWatch.delete(childToAppend);
  }
  return appendChild.call(this, childToAppend);
};



button.addEventListener('click', () => {
  console.log('Element created...');
  const div = document.createElement('div');
  elementsToWatch.add(div);
  setTimeout(() => {
    console.log('About to append element...');
    container.appendChild(div);
  }, 1000);
});
<button id="button">Append something after 1000ms</button>
<div id="container"></div>

不过,改变内置原型通常不是一个好主意。

另一种选择是对整个文档使用 MutationObserver,但这很可能会导致频繁发生突变的大页面的大量激活回调,这可能是不可取的:

const elementsToWatch = [];
new MutationObserver(() => {
  // instead of the below, another option is to iterate over elements
  // observed by the MutationObserver
  // which could be more efficient, depending on how often
  // other elements are added to the page
  const root = document.documentElement; // returns the <html> element
  const indexOfElementThatWasJustAdded = elementsToWatch.findIndex(
    elm => root.contains(elm)
  );
  // instead of the above, could also use `elm.isConnected()` on newer browsers
  
  // if an appended node, if it has a parent,
  // will always be in the DOM,
  // instead of `root.contains(elm)`, can use `elm.parentElement`

  if (indexOfElementThatWasJustAdded === -1) {
    return;
  }
  elementsToWatch.splice(indexOfElementThatWasJustAdded, 1);
  console.log('Observed an appended element!');
}).observe(document.body, { childList: true, subtree: true });


button.addEventListener('click', () => {
  console.log('Element created...');
  const div = document.createElement('div');
  div.textContent = 'foo';
  elementsToWatch.push(div);
  setTimeout(() => {
    console.log('About to append element...');
    container.appendChild(div);
  }, 1000);
});
<button id="button">Append something after 1000ms</button>
<div id="container"></div>

【讨论】:

  • closest() 在这里似乎相当昂贵,您可以通过检查 .parentNode 来捷径,甚至只是 isConnected(我仍然不确定 OP 在 parentNode 更改和 isConnected 之间想要什么)。此外,还有其他方法可以覆盖insertBeforeinsertAdjacentHTMLreplaceChild 等。最后,为什么只观察身体? OP 很可能会产生它们也会附加到头部的元素。
  • 我不确定元素在插入页面之前是否有可能被附加到其他内容 - 如果是这样,则需要 .closest(或 .contains),否则,是的,parentElement 很好。 insertAdjacentHTML 只插入 text,而不是元素,对吧?如果实际元素存在,则只能引用一个元素(从而对其进行处理)
  • 哦,isConnected 好用,以前没听说过!
  • 对不起,我打错了insertAdjacentElement我的意思。
  • 再想一想,对于不支持isConnected的IE>9,我们可以使用compareDocumentPositionif(!("isConnected" in Node.prototype)) { Object.defineProperty(Node.prototype, 'isConnected', { get: function() { return (this.ownerDocument.compareDocumentPosition(this) &amp; Node.DOCUMENT_POSITION_DISCONNECTED) === 0; } }) }制作一个*polyfill*(?)。请注意,虽然我并没有真正检查过 isConnected 的规范,但在某些情况下这可能会中断......
猜你喜欢
  • 2018-08-18
  • 2022-01-08
  • 2014-03-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-08-20
  • 1970-01-01
相关资源
最近更新 更多