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 |2 Answers
Reset to default 1I 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.
#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:scope
and:not()
(the one in the code snippet) does not work. – Quentin Misslin Commented 9 hours ago