使用 querySelectorAll 检索直接子代

IT技术 javascript dom css-selectors
2021-02-10 09:25:19

我能够做到这一点:

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

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

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

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

有谁知道如何编写一个选择器,它只获取选择器正在运行的元素的直接子元素?

6个回答

好问题。在被问到时,还没有一种普遍实现的方式来进行“基于组合器的查询”(正如John Resig 所说的那样)。

现在:scope伪类已经被引入。Edge 或 IE 的 [pre-Chrominum] 版本支持,但 Safari 已经支持它几年了。使用它,您的代码可能会变成:

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

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

否则,如果您还不能依赖:scope,我想不出另一种方法来处理您的情况而不添加更多自定义过滤器逻辑(例如 find myDiv.getElementsByClassName("foo")who .parentNode === myDiv),如果您试图支持一个真正的代码路径,显然这并不理想只想将任意选择器字符串作为输入,将匹配列表作为输出!但是如果像我一样,你最终问这个问题只是因为你陷入了思考“你只有一把锤子”的想法,不要忘记DOM 还提供了各种其他工具。

哦,打扰了!我的意思是:你说得完全正确。我已经更新了我的答案,以更清楚地说明它在 OP 的情况下不是一个很好的答案。
2021-03-22 09:25:19
感谢您提供链接(似乎可以明确回答),并感谢您添加术语“基于组合器的查询”。
2021-03-22 09:25:19
John Resig 所说的“基于组合器的查询”,现在 Selectors 4 称它们为relative selectors
2021-03-27 09:25:19
myDiv.getElementsByClassName("foo")与 不同myDiv.querySelectorAll("> .foo"),它更像是myDiv.querySelectorAll(".foo")(顺便说一下,实际上是有效的),因为它可以找到所有后代.foos 而不是仅儿童。
2021-03-30 09:25:19

有谁知道如何编写一个选择器,它只获取选择器正在运行的元素的直接子元素?

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

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

但是, 浏览器支持是有限的,如果你想使用它,你需要一个垫片。为此目的构建了scopedQuerySelectorShim

2021-03-14 09:25:19
值得一提的是,该:scope规范目前是“工作草案”,因此可能会发生变化。如果/当它被采用时,它可能仍然会像这样工作,但在我看来这是“正确的方法”,这有点早了。
2021-03-16 09:25:19
@Adrian Moisa: :scope 在任何地方都没有被弃用。您可能会将它与 scoped 属性混淆。
2021-03-22 09:25:19
看起来它在此期间已被弃用
2021-03-27 09:25:19
3 年后,你拯救了我的臀大肌!
2021-04-10 09:25:19

这是一个用 vanilla 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;
}
JavaScript 不是并行执行的,因此可能性实际上为零。
2021-03-18 09:25:19
@WGH 哇,我简直不敢相信,我简直惊呆了,我竟然会在这么简单的事情上放屁(我在 Mozilla 工作并经常背诵这个事实,要多睡一会儿!大声笑)
2021-03-18 09:25:19
我建议您在生成的 ID 中添加某种时间戳或计数器。有一个非零(虽然很小)的概率Math.random().toString(36).substr(2, 10)产生相同的令牌不止一次。
2021-03-21 09:25:19
我不确定重复哈希的可能性有多大,ID 只是暂时应用了几分之一毫秒,并且任何欺骗也需要是同一父节点的子节点 - 基本上,机会是天文数字。无论如何,添加一个计数器似乎没问题。
2021-04-07 09:25:19
不确定你的意思any dupe would need to also be a child of the same parent nodeid属性是文档范围的。您说得对,赔率仍然可以忽略不计,但感谢您走高路并添加该计数器:)
2021-04-08 09:25:19

如果您确定该元素是唯一的(例如您使用 ID 的情况):

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

对于更“全局”的解决方案:(使用matchesSelector 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;
}

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

该解决方案使用matchesSelector无前缀(在最新版本的 Chrome 中甚至不起作用),它会污染全局命名空间(未声明 ret),它不会像 querySelectorMethods 预期那样返回 NodeList。我认为这不是一个好的解决方案,因此投了反对票。
2021-03-26 09:25:19
@lazd 这不是问题的一部分。
2021-03-27 09:25:19
您的答案不是“全球”解决方案。它有应该注意的特定限制,因此我的评论。
2021-04-05 09:25:19
@lazd 回答了所问的问题。那么为什么要投反对票呢?或者你只是碰巧在对一年前的答案投反对票的几秒钟内发表评论?
2021-04-09 09:25:19
@lazd - 原始问题使用无前缀函数和全局命名空间。对于给出的问题,这是一个非常好的解决方法。如果您认为这不是一个好的解决方案,请不要使用它,或建议进行编辑。如果它在功能上不正确或垃圾邮件,则考虑投反对票。
2021-04-12 09:25:19

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

基本原理是您首先选择所有匹配的孩子,然后过滤掉那些不是直接孩子的孩子。如果孩子没有具有相同选择器的匹配父级,则它是直接子级。

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

哼!

我喜欢它的简洁性和方法的简单性,尽管如果使用大树和过度贪婪的选择器在高频下调用它很可能不会很好地执行。
2021-03-16 09:25:19