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

时间:2019-06-15 08:30:45

标签: javascript mutation-observers selectors-api

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

如果没有对选择器进行硬编码并使用if语句,这很难做到,我想到了以下所有失败的方法:

  • 尝试使用添加的节点的父节点的querySelectorAll,但是它包含的元素并不只是添加的。
  • 使用添加的节点querySelectorAll函数并合并所有结果,但是由于不包括添加的节点本身,因此不起作用。
  • 创建一个新元素并将所有添加的节点移到该元素上,并在该元素上调用querySelectorAll,但是这些节点在MutationObserver运行后消失,并且没有添加。

有没有办法做到这一点,或者对我想出的一种修改方式使其起作用?

2 个答案:

答案 0 :(得分:1)

addedNodes是一个NodeList集合。您可以通过调用querySelectorAll来实现与Array.prototype.filter几乎相同的功能,其中回调函数检查选择器是否通过了给定元素.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 any 个子元素是否与选择器匹配,而不仅仅是addedNodes本身,则可以使用{{1} },以便从每个元素中提取子匹配项数组:

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

答案 1 :(得分:1)

我确定您知道,您的回调函数收到一个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>