DOM:迭代不包括匹配相同选择器的嵌套元素的元素

问题描述 投票:0回答:3

(请不要使用JQUERY) 假设我有以下树。我想迭代从

querySelectorAll('div.bar')
开始匹配
div.foo
的元素,但不迭代匹配相同选择器的子元素。我该怎么做?

div.foo
  div.bar -- select
  div.bar -- select
    div.bar -- skip
    div.bar -- skip
  div.damnSon
    div.bar -- select
    div.bar -- select
       div.bar --skip
  div.bar -- select
    div.bar -- skip

TreeWalker
是不行的,因为

  • 它完全拒绝该元素,而我仍然想处理它,只是不是它的子元素
  • 它仅适用于可见元素,而我也可能会迭代隐藏元素 由于同样的问题,
    NodeIterator
    也不起作用,即使您拒绝父级,它仍然会迭代子级。

最理想的情况是以某种方式获得不平坦的

querySelector
结果。然后我只需循环它们而不必关心嵌套属性。

javascript dom css-selectors
3个回答
0
投票
var elems=Array.prototype.filter.call(querySelectorAll('div.bar'),function(el){
  var foochild=false;
  while(el=el.parent){
    if(el.classList.contains("bar") return false;//fail as its a div.bar children
    if(el.classList.contains("foo")) foochild=true;//were a children of foo
   }
   return foochild;
});

只需在 dom 树中过滤有关其父母的 div.bars 即可。


另一种方法是迭代所有直接 foo 子代:

Array.prototype.forEach.call(document.querySelectorAll("div.foo"),function iterate(foo){
  for(var i=0;i<foo.children.length){
   var child=foo.children[i];
   if(child.classList.contains("bar")){
     //weve found a bar
   }else{
    iterate(child);//find subbars
   }
  }
});

0
投票

到目前为止我想出了以下戒酒方法:

let nodeIterator = document.createNodeIterator(root, NodeFilter.SHOW_ELEMENT);
let node;
while(node = nodeIterator.nextNode()){
  if (node.matches('.bar .bar')) {
    //skip
  } else if (node.matches('.bar')) {
    //do things
  }
}

这样做的问题是:

  • 它很慢 - 特别是在 SVG 或任何具有许多元素的东西中
  • 还是平的。
  • 如果
    div.foo
    (即我的根元素)本身嵌套在 (
    div.bar
    )
  • 中,它将中断

如果有人可以建议更好的方法,我们将不胜感激。


更新: 好吧,所以我想出了以下方法,这解决了最后一点,但效率又不那么低:

let bars = root.getElementsByClassName('bar');
let excludedChildren = [];
for(let i = 0; i < bars.length; i++){
  let children = bars[i].getElementsByClassName('bar');
  if(children.length>0) Array.prototype.push.apply(excludedChildren, children);
}
bars = bars.filter(e=>!excludedChildren.includes(e));
bars.forEach(e=>{
  //do stuff
});

更新: 是时候结束这个旧事了,这是我的最终解决方案。目前已投入生产一个月,没有出现任何问题。当然,这无法处理一些非常复杂的情况,但它是可扩展的。


0
投票

您可以在 CSS 选择器中使用

:not
排除嵌套元素,例如:

querySelectorAll('div.bar:not(div.bar div.bar)')

codepen 上查看它或运行代码片段:

let foo = document.querySelector("div.foo");
let bars = foo.querySelectorAll("div.bar:not(div.bar div.bar)");
bars.forEach((div) => {
  div.style.border = "3px solid black";
  div.style.backgroundColor = "yellow";
});
div {
  padding-left: 1em;
  margin-bottom: 3px;
}
.foo {
  width: 200px;
}
.bar::before {
  content: "bar";
}
<div class="foo">
  <div class="bar"></div>
  <div class="bar">
    <div class="bar"></div>
    <div class="bar"></div>
  </div>
  <div class="damnSon">
    <div class="bar"></div>
    <div class="bar">
      <div class="bar"></div>
    </div>
  </div>
  <div class="bar">
    <div class="bar"></div>
  </div>
</div>

© www.soinside.com 2019 - 2024. All rights reserved.