使用querySelectorAll检索直接子级


119

我能够做到这一点:

<div id="myDiv">
   <div class="foo"></div>
</div>
myDiv = getElementById("myDiv");
myDiv.querySelectorAll("#myDiv > .foo");

也就是说,我可以成功检索myDiv具有class 的元素的所有直接子级.foo

问题是,我必须#myDiv在选择器中包含,这使我感到困扰,因为我正在myDiv元素上运行查询(因此显然是多余的)。

我应该能够离开#myDiv,但是选择器不是合法的语法,因为它以a开头>

有谁知道如何编写一个选择器,该选择器仅获取运行选择器的元素的直接子代?



没有任何答案能满足您的需求吗?请提供反馈或选择答案。
兰迪·霍尔

@mattsh-我为您的需要添加了一个更好的(IMO)答案,请查看!
csuwldcat

Answers:


85

好问题。在被问到的时候,尚不存在一种通用的方法来执行“组合根查询”(如John Resig所称)。

现在,介绍了:scope伪类。Edge或IE的[Chrominum]版本不支持此功能,但Safari已有几年支持了。使用该代码,您的代码可能变为:

let myDiv = getElementById("myDiv");
myDiv.querySelectorAll(":scope > .foo");

请注意,在某些情况下,您还可以跳过.querySelectorAll并使用其他良好的老式DOM API功能。例如,代替myDiv.querySelectorAll(":scope > *")您可以只写myDiv.children

否则,如果您还不能依靠:scope,我想不出另一种方式来处理您的情况,而无需添加更多的自定义过滤器逻辑(例如,找到myDiv.getElementsByClassName("foo")谁的过滤器.parentNode === myDiv),并且如果您想真正支持一个真正的代码路径,显然不是理想的选择只想将任意选择器字符串作为输入,并将匹配项列表作为输出!但是,如果像我一样,您最终只是因为只是想着“只有锤子而已”就问了这个问题,别忘了DOM也提供了许多其他工具。


3
myDiv.getElementsByClassName("foo")与并不相同myDiv.querySelectorAll("> .foo"),它更像是myDiv.querySelectorAll(".foo")(实际上是通过某种方式工作的),因为它找到了所有后代.foos而不是子代。
BoltClock

哦,麻烦了!我的意思是:您完全正确。我已经更新了我的答案,以使其更加清楚地表明,对于OP来说,这不是一个很好的答案。
natevw

感谢您提供的链接(似乎可以肯定地回答这个问题),并感谢您添加术语“组合器查询”。
mattsh

1
John Resig所说的“基于组合器的查询”,选择器4现在将它们称为相对选择器
BoltClock

@Andrew范围的样式表(即<style scoped>)与:scope此答案中描述的伪选择器无关。
natevw

124

有谁知道如何编写一个选择器,该选择器仅获取运行选择器的元素的直接子代?

编写“植根”到当前元素的选择器的正确方法是使用:scope

var myDiv = getElementById("myDiv");
var fooEls = myDiv.querySelectorAll(":scope > .foo");

但是, 浏览器支持有限,如果要使用它,则需要垫片。为此,我构建了scopedQuerySelectorShim


3
值得一提的是,该:scope规范目前是“工作草案”,因此可能会发生变化。如果/当采用时,它可能仍会像这样工作,但是我认为这是“正确的方式”来做。
Zach Lysobey 2015年

2
看起来它已被弃用
Adrian Moisa

1
3年后,您救了我的臀大肌!
Mehrad '18年

5
@Adrian Moisa::scope在任何地方都没有被弃用。您可能将其与scoped属性混淆了。
BoltClock

6

这是一个用香草JS编写的灵活方法,它允许您仅对元素的直接子代运行CSS选择器查询:

var count = 0;
function queryChildren(element, selector) {
  var id = element.id,
      guid = element.id = id || 'query_children_' + count++,
      attr = '#' + guid + ' > ',
      selector = attr + (selector + '').replace(',', ',' + attr, 'g');
  var result = element.parentNode.querySelectorAll(selector);
  if (!id) element.removeAttribute('id');
  return result;
}

我建议您为生成的ID添加某种时间戳或计数器。Math.random().toString(36).substr(2, 10)多次产生相同令牌的可能性不为零(尽管很小)。
弗雷德里克·哈米迪

我不确定给定重复哈希的几率是微小的,ID只是暂时应用了不到一毫秒,并且任何重复都必须是同一父节点的子节点-基本上,机会是天文数字。无论如何,添加一个计数器似乎很好。
csuwldcat

不确定您的意思是any dupe would need to also be a child of the same parent nodeid属性在整个文档范围内。你是对的胜算还是相当微不足道的,但感谢你走的是高端路线,并补充说,反:)
弗雷德里克·哈米迪

8
JavaScript不是并行执行的,因此机会实际上为零。
WGH 2013年

1
@WGH哇,我简直不敢相信,我绝对会为这样一个简单的问题而放屁(我在Mozilla工作,经常背诵这个事实,必须多睡一会!大声笑)
csuwldcat

5

如果您确定该元素是唯一的(例如您的ID大小写的情况):

myDiv.parentElement.querySelectorAll("#myDiv > .foo");

对于更“全局”的解决方案:(使用matchsSelector shim

function getDirectChildren(elm, sel){
    var ret = [], i = 0, l = elm.childNodes.length;
    for (var i; i < l; ++i){
        if (elm.childNodes[i].matchesSelector(sel)){
            ret.push(elm.childNodes[i]);
        }
    }
    return ret;
}

where elm是您的父元素,sel也是您的选择器。也可以完全用作原型。


@lazd并不是问题的一部分。
兰迪·霍尔

您的答案不是“全局”解决方案。它具有应注意的特定限制,因此请发表评论。
lazd

@lazd回答了所问的问题。那为什么要下票呢?还是您刚好在对一年前答案否决的几秒钟内发表评论?
兰迪·霍尔

该解决方案使用matchesSelector无前缀的(甚至在最新版本的Chrome中也不起作用),污染了全局名称空间(未声明ret),并且不返回NodeList,如期望的querySelectorMethods一样。我认为这不是一个好的解决方案,因此不赞成。
lazd 2014年

@lazd-原始问题使用无前缀的函数和全局名称空间。对于给出的问题,这是一个非常好的解决方法。如果您认为这不是一个好的解决方案,请不要使用它或建议进行编辑。如果它在功能上不正确或为垃圾邮件,请考虑投票。
兰迪·霍尔

2

以下解决方案与到目前为止提出的解决方案不同,并且对我有用。

这样做的理由是,首先选择所有匹配的子代,然后过滤掉不是直接子代的子代。如果子代没有一个具有相同选择器的匹配父代,则该子代为直接子代。

function queryDirectChildren(parent, selector) {
    const nodes = parent.querySelectorAll(selector);
    const filteredNodes = [].slice.call(nodes).filter(n => 
        n.parentNode.closest(selector) === parent.closest(selector)
    );
    return filteredNodes;
}

HTH!


我喜欢它的简洁性和方法的简单性,尽管如果高频率调用大型树和过于贪婪的选择器,它很可能无法很好地执行。
brennanyoung

1

我创建了一个函数来处理这种情况,以为可以共享。

getDirectDecendent(elem, selector, all){
    const tempID = randomString(10) //use your randomString function here.
    elem.dataset.tempid = tempID;

    let returnObj;
    if(all)
        returnObj = elem.parentElement.querySelectorAll(`[data-tempid="${tempID}"] > ${selector}`);
    else
        returnObj = elem.parentElement.querySelector(`[data-tempid="${tempID}"] > ${selector}`);

    elem.dataset.tempid = '';
    return returnObj;
}

本质上,您正在执行的操作是生成一个随机字符串(这里的randomString函数是一个导入的npm模块,但是您可以自己创建一个。),然后使用该随机字符串来确保您在选择器中获得了期望的元素。然后您就可以自由使用了>

我不使用id属性的原因是id属性可能已经被使用,并且我不想覆盖它。


0

好了,我们可以轻松地使用来获取元素的所有直接子元素,childNodes并且可以使用选择特定类的祖先querySelectorAll,因此不难想象我们可以创建一个可以同时获取和比较两者的新函数。

HTMLElement.prototype.queryDirectChildren = function(selector){
  var direct = [].slice.call(this.directNodes || []); // Cast to Array
  var queried = [].slice.call(this.querySelectorAll(selector) || []); // Cast to Array
  var both = [];
  // I choose to loop through the direct children because it is guaranteed to be smaller
  for(var i=0; i<direct.length; i++){
    if(queried.indexOf(direct[i])){
      both.push(direct[i]);
    }
  }
  return both;
}

注意:这将返回节点数组,而不是NodeList。

用法

 document.getElementById("myDiv").queryDirectChildren(".foo");

0

我想补充一点,您可以通过将临时属性分配给当前节点来扩展:scope的兼容性。

let node = [...];
let result;

node.setAttribute("foo", "");
result = window.document.querySelectorAll("[foo] > .bar");
// And, of course, you can also use other combinators.
result = window.document.querySelectorAll("[foo] + .bar");
result = window.document.querySelectorAll("[foo] ~ .bar");
node.removeAttribute("foo");

-1

我会和

var myFoo = document.querySelectorAll("#myDiv > .foo");
var myDiv = myFoo.parentNode;

-2

我只是在没有尝试的情况下这样做。这行得通吗?

myDiv = getElementById("myDiv");
myDiv.querySelectorAll(this.id + " > .foo");

试试看,也许行不通。致歉,但我现在不在电脑上尝试(通过iPhone回复)。


3
QSA的作用域仅限于它被调用的元素,因此它将在myDiv中查找与myDiv具有相同ID的另一个元素,然后在其子元素中使用.foo。你可以做类似`document.querySelectorAll('#'+ myDiv.id +'> .foo');的事情。
大概
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.