【问题标题】:how to use querySelectorAll on the added nodes in a MutationObserver如何在 MutationObserver 中添加的节点上使用 querySelectorAll
【发布时间】:2019-10-29 17:10:23
【问题描述】:

我试图在MutationObserver 中创建一个querySelectorAll 函数,所以它就像调用querySelectorAll 到新添加的元素一样。这样做的原因是它可以与其他现有代码一起使用。

事实证明,如果没有硬编码选择器和使用if 语句,这很难,我想到了以下所有失败的方法:

  • 尝试使用已添加节点的父节点的querySelectorAll,但随后会包含不只是添加的元素。
  • 使用添加节点querySelectorAll函数并合并所有结果,但由于不包含添加节点本身,它不起作用。
  • 创建一个新元素并将所有添加的节点移至其中,并在该元素上调用querySelectorAll,但随后在MutationObserver 运行后节点消失并且不会被添加。

有没有办法做到这一点,或者对我想出的一种方法进行一些修改以使其有效?

【问题讨论】:

  • @T.J.Crowder 我会尝试,但有点难,因为这是 Google 浏览器扩展。

标签: javascript mutation-observers selectors-api


【解决方案1】:

我相信你知道,你的回调会收到一个 MutationRecords 数组,每个数组都有一个名为 addedNodes 的添加节点的 NodeList

将它们转换为与选择器匹配的元素列表可能比理想情况下更复杂,但这里有一种方法(参见 cmets):

function applySelector(selector, records) {
    // We can't create a NodeList; let's use a Set
    const result = new Set();
    // Loop through the records...
    for (const {addedNodes} of records) {
        for (const node of addedNodes) {
            // If it's an element...
            if (node.nodeType === 1) {
                // Add it if it's a match
                if (node.matches(selector)) {
                    result.add(node);
                }
                // Add any children
                addAll(result, node.querySelectorAll(selector));
            }
        }
    }
    return [...result]; // Result is an array, or just return the set
}

现场示例:

const ob = new MutationObserver(records => {
    const result = applySelector("span", records);
    console.log(`Got ${result.length} matches:`);
    for (const span of result) {
        console.log(span.id);
    }
});
const target = document.getElementById("target");
ob.observe(target, {childList: true});
target.insertAdjacentHTML(
    "beforeend",
    `<div>
      blah
      <span id="span1">span</span>
      blah
      <div>
        <span id="span2">lorem <span id="span3">ipsum</span></span>
      </div>
    </div>`
);

function addAll(set, list) {
    for (const entry of list) {
        set.add(entry);
    }
}
function applySelector(selector, records) {
    // We can't create a NodeList; let's use a Set
    const result = new Set();
    // Loop through the records...
    for (const {addedNodes} of records) {
        for (const node of addedNodes) {
            // If it's an element...
            if (node.nodeType === 1) {
                // Add it if it's a match
                if (node.matches(selector)) {
                    result.add(node);
                }
                // Add any children
                addAll(result, node.querySelectorAll(selector));
            }
        }
    }
    return [...result]; // Result is an array, or just return the set
}
&lt;div id="target"&gt;&lt;/div&gt;

【讨论】:

  • 嗯,谢谢,我想这比我想象的要复杂一些,但它确实有效。感谢您告诉我有关 .matches 功能的信息,这至少可以避免硬编码。
  • @simonzack - 是的,matches 很棒。 :-)
【解决方案2】:

addedNodes 是一个 NodeList 集合。您可以通过在其上调用Array.prototype.filter 来实现与querySelectorAll 几乎相同的功能,其中回调检查给定元素.matches 选择器是否通过:

new MutationObserver((mutationsList) => {
  const { addedNodes } = mutationsList[0];
  const matches = [...addedNodes]
    .filter(node => node.nodeType === 1)
    .filter(element => element.matches('.someDiv'));
  if (matches.length) {
    console.log(matches);
  }
})
  .observe(document.body, { childList: true });
setTimeout(() => {
  document.body.insertAdjacentHTML(
    'beforeend',
    `<div class="someDiv">dynamically added matching</div>
     <div class="nonMatching">dynamically added non-matching</div>`
  );
}, 1000);
&lt;div class="somediv"&gt;existing&lt;/div&gt;

只需将传递给.matches 的选择器字符串替换为您要过滤的任何选择器字符串即可。

如果您想检查addedNodes任何 子元素是否与选择器匹配,而不仅仅是addedNodes 本身,您可以使用flatMap 之类的东西来提取数组每个元素的子匹配项,如果需要:

new MutationObserver((mutationsList) => {
  const { addedNodes } = mutationsList[0];
  const elements = [...addedNodes]
    .filter(node => node.nodeType === 1);
  const matches = [
    ...elements.filter(element => element.matches('.someDiv')),
    ...elements.flatMap(element => [...element.querySelectorAll('.someDiv')])
  ];
  if (matches.length) {
    console.log(matches);
  }
})
  .observe(document.body, { childList: true });
setTimeout(() => {
  document.body.insertAdjacentHTML(
    'beforeend',
    `<div class="someDiv">dynamically added matching
       <div class="someDiv">dynamically added matching nested</div>
    </div>
     <div class="nonMatching">dynamically added non-matching</div>`
  );
}, 1000);
&lt;div class="somediv"&gt;existing&lt;/div&gt;

【讨论】:

  • 添加的匹配的元素的后代元素呢? :-)
  • 嗯也许我没有解释清楚,但目标是有一个代理querySelectorAll的函数,我已经尝试使用filter,当前的硬编码解决方案使用生成器.
  • @simonzack querySelectorAll 仅在 Document 或元素上运行 - 它不在 NodeList 上运行,因此您不能直接使用它 - 例如 .filter.matches是最好的选择。
  • @simonzack - 诀窍是因为观察者的回调得到一个记录数组,然后有添加节点的 NodeLists,你基本上必须将 querySelectorAll 添加到 Array.prototype,这可能会不理想。 :-)
猜你喜欢
  • 2017-10-10
  • 2017-12-10
  • 2016-10-25
  • 1970-01-01
  • 2012-11-18
  • 2021-06-17
  • 1970-01-01
  • 2011-09-12
  • 1970-01-01
相关资源
最近更新 更多