278

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, or null 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 tag.

0

5 Answers 5

262

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.

12
  • Figured as much, though one thing still bugs me: the whitespace textnode that childNodes picks up on is actually just a new line and a couple of tabs in the code. Is this due to the XHTML doctype? can this be resolved by enclosing the whitespace to struct code within the tags? Commented Apr 30, 2012 at 10:07
  • @EliasVanOotegem: That's unrelated to doctype. White space text nodes are always included in the DOM (except in IE < 9) for both HTML and XHTML, wherever they appear in the source. It's generally a good thing, although it can catch you out if you're not prepared for it.
    – Tim Down
    Commented Apr 30, 2012 at 10:32
  • I meant if it would help to write my markup like this <td\n\t>content</td>. As you might do with XML, to avoid excess whitespace being regarded as part of the data (or DOM, in this case). Why would whitespace inside a tag be included in the DOM? Commented Apr 30, 2012 at 10:42
  • @EliasVanOotegem: Oh, I see. White space after the tag name inside a tag is ignored, so you could do that kind of thing. I've seen that technique used in precise layouts to prevent browsers adding tiny gaps for white space between some kinds of elements.
    – Tim Down
    Commented Apr 30, 2012 at 10:48
  • 2
    @Christophe: Ah, fair point, thanks. I'll add a note to my answer. Given that children originated in IE 4 over a decade before becoming a standard or being adopted by other browsers, you could argue that IE <= 8 is correct and other browsers are wrong.
    – Tim Down
    Commented Sep 12, 2013 at 8:26
25

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

9
  • If elem.firstChild is not an element node, the results will be different in old IEs, because elem.firstElementChild is always element node.
    – duri
    Commented Apr 30, 2012 at 9:36
  • I'm sorry, but I know how to get the first child in all major browsers. What I want to know is how the different objects are structured, in order to get a better understanding of the similarities and differences between them. I'll edit my question later to make that clearer, sorry for the mixup Commented Apr 30, 2012 at 9:37
  • 1
    firstElementChild is not necessarily safe in non-IE browsers either: Firefox only started supporting it in version 3.5.
    – Tim Down
    Commented Apr 30, 2012 at 9:49
  • this is working for chrome, IE9 and IE8 because if they have the firstElementChild then the other check is not executed, otherwise(old IE) we have the firstChild, and it is an element node for browser that do not have firstElementChild (IE<9)... still i wonder about FF
    – neu-rah
    Commented Apr 30, 2012 at 10:03
  • 1
    Tim is correct, when I was googling stuff on these objects this showed up, good site to check once in a while Commented Apr 30, 2012 at 10:19
23

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.

2
5

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.

4

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>
1
  • 1
    Not my down-vote, but I think somebody down-voted because: a) This question is 4 years old, document.querySelector wasn't well supported back then. b) The question is basically asking what the differences are between children and childNodes internally, which is more reliable, when to choose one over the other. and c) Because, as demonstraded in the question: childNodes does include whitespace nodes, children does not... Commented Mar 31, 2016 at 9:51

Not the answer you're looking for? Browse other questions tagged or ask your own question.