Best way to get child nodes
I was wondering, JavaScript offers a variety of methods to get the first child element from any element, but which is the best? By best, I mean: most cross-browser compatible, fastest, most comprehensive and predictable when it comes to behaviour. A list of methods/properties I use as aliases:
var elem = document.getElementById('container');
var child = elem.children[0];
var child = elem.firstElementChild; // == children[0]
This works for both cases:
var child = elem.childNodes[0]; // or childNodes[1], see below
That’s in case of forms, or <div>
iteration. If I might encounter text elements:
var child = elem.childNodes; // treat as NodeList
var child = elem.firstChild;
As far as I can work out, firstChild
uses the NodeList from childNodes
, and firstElementChild
uses children
. I’m basing this assumption on the MDN reference:
childNode
is a reference to the first child element of the element node, ornull
if there isn’t one.
I’m guessing that, in terms of speed, the difference, if any, will be next to nothing, since firstElementChild
is effectively a reference to children[0]
, and the children
object is already in memory anyway.
What does throw me, is the childNodes
object. I’ve used it to take a look at a form, in a table element. While children
lists all form elements, childNodes
also seems to include whitespace from the HTML code:
console.log(elem.childNodes[0]);
console.log(elem.firstChild);
Both log <TextNode textContent="\n ">
console.log(elem.childNodes[1]);
console.log(elem.children[0]);
console.log(elem.firstElementChild);
All log <input type="text"
…>
. How come? I’d understand that one object would allow me to work with the “raw” HTML code, while the other sticks to the DOM, but the childNodes
element seems to work on both levels.
To get back to my initial question, my guess would be: if I want the most comprehensive object, childNodes
is the way to go, but because of its comprehensiveness, it might not be the most predictable in terms of it returning the element I want/expect at any given moment. Cross-browser support might also prove to be a challenge in that case, though I could be wrong.
Could anyone clarify the distinction between the objects at hand? If there is a speed difference, however negligible, I’d like to know, too. If I’m seeing this all wrong, feel free to educate me.
PS: Please, please, I like JavaScript, so yes, I want to deal with this sort of thing. Answers like “jQuery deals with this for you” are not what I’m looking for, hence no jquery tag.
Solution 1:
Sounds like you're overthinking it. You've observed the difference between childNodes
and children
, which is that childNodes
contains all nodes, including text nodes consisting entirely of whitespace, while children
is a collection of just the child nodes that are elements. That's really all there is to it.
There is nothing unpredictable about either collection, although there are a couple of issues to be aware of:
- IE <= 8 do not include white space-only text nodes in
childNodes
while other browsers do - IE <= 8 includes comment nodes within
children
while other browsers only have elements
children
, firstElementChild
and friends are just conveniences, presenting a filtered view of the DOM restricted to just elements.
Solution 2:
firstElementChild might not be available in IE<9 (only firstChild)
on IE<9 firstChild is the firstElementChild because MS DOM (IE<9) is not storing empty text nodes. But if you do so on other browsers they will return empty text nodes...
my solution
child=(elem.firstElementChild||elem.firstChild)
this will give the firstchild even on IE<9
Solution 3:
The cross browser way to do is to use childNodes
to get NodeList
, then make an array of all nodes with nodeType
ELEMENT_NODE.
/**
* Return direct children elements.
*
* @param {HTMLElement}
* @return {Array}
*/
function elementChildren (element) {
var childNodes = element.childNodes,
children = [],
i = childNodes.length;
while (i--) {
if (childNodes[i].nodeType == 1) {
children.unshift(childNodes[i]);
}
}
return children;
}
http://jsfiddle.net/s4kxnahu/
This is especially easy if you are using a utility library such as lodash:
/**
* Return direct children elements.
*
* @param {HTMLElement}
* @return {Array}
*/
function elementChildren (element) {
return _.where(element.childNodes, {nodeType: 1});
}
Future:
You can use querySelectorAll
in combination with :scope
pseudo-class (matches the element that is the reference point of the selector):
parentElement.querySelectorAll(':scope > *');
At the time of writing this :scope
is supported in Chrome, Firefox and Safari.
Solution 4:
Just to add to the other answers, there are still noteworthy differences here, specifically when dealing with <svg>
elements.
I have used both .childNodes
and .children
and have preferred working with the HTMLCollection
delivered by the .children
getter.
Today however, I ran into issues with IE/Edge failing when using .children
on an <svg>
.
While .children
is supported in IE on basic HTML elements, it isn't supported on document/document fragments, or SVG elements.
For me, I was able to simply grab the needed elements via .childNodes[n]
because I don't have extraneous text nodes to worry about. You may be able to do the same, but as mentioned elsewhere above, don't forget that you may run into unexpected elements.
Hope this is helpful to someone scratching their head trying to figure out why .children
works elsewhere in their js on modern IE and fails on document or SVG elements.
Solution 5:
Don't let white space fool you. Just test this in a console browser.
Use native javascript. Here is and example with two 'ul' sets with the same class. You don't need to have your 'ul' list all in one line to avoid white space just use your array count to jump over white space.
How to get around white space with querySelector()
then childNodes[]
js fiddle link: https://jsfiddle.net/aparadise/56njekdo/
var y = document.querySelector('.list');
var myNode = y.childNodes[11].style.backgroundColor='red';
<ul class="list">
<li>8</li>
<li>9</li>
<li>100</li>
</ul>
<ul class="list">
<li>ABC</li>
<li>DEF</li>
<li>XYZ</li>
</ul>