如何在 MutationObserver 中添加的节点上使用 querySelectorAll

how to use querySelectorAll on the added nodes in a MutationObserver

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

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

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

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);
<div class="somediv">existing</div>

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

如果你想检查addedNodes任何子元素是否匹配选择器,而不仅仅是addedNodes本身,你可以使用一些东西像 flatMap 从每个元素中提取 sub-matches 的数组,如果你想:

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);
<div class="somediv">existing</div>

我相信您知道,您的回调接收到一个 MutationRecord 数组,每个数组都有一个 NodeList 个添加的节点,称为 addedNodes.

将它们变成与选择器匹配的元素列表可能比理想情况下更复杂,但这是一种方法(参见评论):

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
}
<div id="target"></div>