最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

dom - Get descendant children with a specific class efficiently in vanilla Javascript - Stack Overflow

programmeradmin5浏览0评论

I'm currently using the following JavaScript function to get all direct .layout children of a given parent, ensuring that the selected elements are:

  • Not nested inside another .layout
  • Directly inside the specified parent
  • The given parent element may or may not have the .layout class

Here’s my implementation:

const getDirectChildren = (parent) => {
  return [...parent.querySelectorAll('.layout')].filter(layout => {
    const closest = layout.parentNode?.closest('.layout');
    return closest == null || closest === parent;
  });
}

Expected Behavior Example

Given the following HTML structure:

<div class="parent">
  <div class="layout" id="layout-1"></div>   <!-- Direct child : Yes -->
  <div class="a">
    <div class="layout" id="layout-2"></div> <!-- Indirect child : Yes -->
  </div>
  <div class="layout" id="layout-3">         <!-- Direct child : Yes -->
    <div class="layout" id="layout-4"></div> <!-- Indirect child with .layout parent : No -->
  </div>
</div>

If I call getDirectChildren(document.querySelector('.parent')), the expected output should be:

[
  <div class="layout" id="layout-1"></div>,
  <div class="layout" id="layout-2"></div>,
  <div class="layout" id="layout-3"></div>
]

My implementation works as expected, but I’m wondering: Is there a more optimized approach to achieve the same result while reducing the number of DOM queries?

Any suggestions for improving the performance of this function? I'm looking for a vanilla JavaScript solution only.

I'm currently using the following JavaScript function to get all direct .layout children of a given parent, ensuring that the selected elements are:

  • Not nested inside another .layout
  • Directly inside the specified parent
  • The given parent element may or may not have the .layout class

Here’s my implementation:

const getDirectChildren = (parent) => {
  return [...parent.querySelectorAll('.layout')].filter(layout => {
    const closest = layout.parentNode?.closest('.layout');
    return closest == null || closest === parent;
  });
}

Expected Behavior Example

Given the following HTML structure:

<div class="parent">
  <div class="layout" id="layout-1"></div>   <!-- Direct child : Yes -->
  <div class="a">
    <div class="layout" id="layout-2"></div> <!-- Indirect child : Yes -->
  </div>
  <div class="layout" id="layout-3">         <!-- Direct child : Yes -->
    <div class="layout" id="layout-4"></div> <!-- Indirect child with .layout parent : No -->
  </div>
</div>

If I call getDirectChildren(document.querySelector('.parent')), the expected output should be:

[
  <div class="layout" id="layout-1"></div>,
  <div class="layout" id="layout-2"></div>,
  <div class="layout" id="layout-3"></div>
]

My implementation works as expected, but I’m wondering: Is there a more optimized approach to achieve the same result while reducing the number of DOM queries?

Any suggestions for improving the performance of this function? I'm looking for a vanilla JavaScript solution only.

Share Improve this question edited 10 hours ago Quentin Misslin asked 10 hours ago Quentin MisslinQuentin Misslin 31 bronze badge New contributor Quentin Misslin is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct. 5
  • 1 Direct child and descendant are different things, I reckon you mean descendant rather than direct child. Your description is confusing as it is. – Tekins Commented 10 hours ago
  • 1 I changed the title to make it more relevant by adding the keyword "descendant"! And I replaced "static" with "const" (my function was copied and pasted from my class, which is why it was originally defined as "static"). Thanks for your feedback! – Quentin Misslin Commented 10 hours ago
  • What if #layout-4 did not have .layout but had a child, say, #layout-5 with .layout, i.e. a node whose parent does not but grandparent has .layout that is not the root ancestor? Will that be selected or skipped? – Tekins Commented 9 hours ago
  • I added an answer with a more complete example to better explain the expected result. My initial implementation works well, but the currently provided solution using :scope and :not() (the one in the code snippet) does not work. – Quentin Misslin Commented 9 hours ago
  • I think your code is fine, it gets the job done in one pass. I'm not sure if a complex CSS selector string will perform any better, I would like to see quantifiable comparisons. – Tekins Commented 8 hours ago
Add a comment  | 

2 Answers 2

Reset to default 1

I believe you could use :scope and :not() in order to retrieve a desired NodeList of #layout-1/2/3

const getDirectChildren = (parent, sel = ".layout") =>
  parent.querySelectorAll(`:scope ${sel}:not(${sel} ${sel})`);

console.log(getDirectChildren(document.querySelector(".parent")));
<div class="parent">
  <div class="layout" id="layout-1"></div> <!-- Direct child : Yes -->
  <div class="a">
    <div class="layout" id="layout-2"></div> <!-- Indirect child : Yes -->
  </div>
  <div class="layout" id="layout-3"> <!-- Direct child : Yes -->
    <div class="layout" id="layout-4"></div> <!-- Indirect child with .layout parent : No -->
  </div>
</div>

If you need an Array, use [...parent.querySelectorAll(`:scope ${sel}:not(${sel} ${sel})`)]

Here's the CSS selector in play for a better understanding:

.layout:not(.layout .layout):before {
  content: "✅"
}
<div class="parent">
  <div class="layout" id="layout-1">Direct child : Yes</div>
  <div class="a">
    <div class="layout" id="layout-2">Indirect child : Yes</div>
  </div>
  <div class="layout" id="layout-3">
    Direct child : Yes
    <div class="layout" id="layout-4">Indirect child with .layout parent : No</div>
  </div>
</div>

Thank you @Roko C. Buljan for your help (I wanted to reply as a comment on your post, but my example was too long).

Your approach using :scope and :not() works well for the initial example, but I encountered an issue when applying it to a specific parent element.

If I call getDirectChildren(document.querySelector("#layout-3")) on the initial example, it does not return #layout-4 as expected. I think this happens because the selector :scope .layout:not(.layout .layout) only considers direct .layout elements relative to the original parent (.parent in the example), but it does not properly update when parent itself is a .layout.

Here is an extended example demonstrating the issue:

const getDirectChildren = (parent, sel = ".layout") =>
            [...parent.querySelectorAll(`:scope ${sel}:not(${sel} ${sel})`)];

        // Example 1 : Should print [#layout-1, #layout-2, #layout-7]
        let app = document.querySelector(".app");
        console.log("App descendant children", getDirectChildren(app)); // Result: Correct!

        // Example 2 : Should print [#layout-3, #layout-4, #layout-6]
        let layout2 = document.querySelector("#layout-2");
        console.log("Layout 2 descendant children", getDirectChildren(layout2)); // Current result: []

        // Example 3 : Should print [#layout-5]
        let layout4 = document.querySelector("#layout-4");
        console.log("Layout 4 descendant children", getDirectChildren(layout4)); // Current result: []
<div class="app">
    <div>
        <div class="layout" id="layout-1"></div>
    </div>
    <div>
        <div class="layout" id="layout-2">
            <div class="layout" id="layout-3"></div>
            <div>
                <div class="layout" id="layout-4">
                    <div>
                        <div class="layout" id="layout-5"></div>
                    </div>
                </div>
                <div class="layout" id="layout-6"></div>
            </div>
            <div></div>
        </div>
    </div>
    <div class="layout" id="layout-7"></div>
</div>

This shows that while the function works at the top level, it fails to return children when applied to .layout elements deeper in the hierarchy.

发布评论

评论列表(0)

  1. 暂无评论