Please help me understand this JavaScript function

This function is from JavaScript:The Definitive Guide - 6th Edition, page 374:


/**
* Return the nth element child of e, or null if it doesn't have one.
* Negative values of n count from the end. 0 means the first child, but
* -1 means the last child, -2 means the second to last, and so on.
*/
function child(e, n) {
  if (e.children) { // If children array exists
  if (n < 0) n += e.children.length; // Convert negative n to array index
  if (n < 0) return null; // If still negative, no child
  return e.children[n]; // Return specified child
  }
  // If e does not have a children array, find the first child and count
  // forward or find the last child and count backwards from there.
  if (n >= 0) { // n is non-negative: count forward from the first child
  // Find the first child element of e  <<<---------------------------  This is where code starts to get confusing to me
  if (e.firstElementChild) e = e.firstElementChild;  //    <<<----------- e.children must not exists if we are executing this line of code, so why check for Elements on e again? Maybe he meant to check 'firstChild'?
    else {  <<<--------------------------- if author knows at this point that e argument is not actually an element
    for(e = e.firstChild; e && e.nodeType !== 1; e = e.nextSibling) //  <<<----------- Why does he look for an element here by running loop until nodeType !== 1 ? TextNodes and CommentNodes will never contain Elements
    /* empty */;
    }
    return sibling(e, n); // Return the nth sibling of the first child
  }
  else { // n is negative, so count backwards from the end
  if (e.lastElementChild) e = e.lastElementChild;
  else {
  for(e = e.lastChild; e && e.nodeType !== 1; e=e.previousSibling)
  /* empty */;
  }
  return sibling(e, n+1); // +1 to convert child -1 to sib 0 of last
  }
}

At the line of JavaScript code that I have pointed out above the author has already determined that the element passed is not actually an element so it must then be a TextNode(3) or CommentNode(8). So why does he run a loop looking for a NodeType == 1 on the childNodes of argument e? I may be missing something here but if somebody could show me what it is, that would be great. Thanks for reading.

This is the code in question (braces added);

if (e.firstElementChild) {
    e = e.firstElementChild;
} else {
    for(e = e.firstChild; e && e.nodeType !== 1; e = e.nextSibling) {
        ...
    }
}

That loop is going to keep on looping through the next siblings. Eventually you will get to the end of the siblings, at which point e will be null.
That is why the check - it’s checking to see if you’ve gone past the last sibling.

The reason for that code though, is for if the web browser doesn’t know about the firstElementChild property. If the web browser doesn’t support that technique, the for loop is an old-fashioned and slightly slower, but reliable way to achieve it instead.

But unless I am mistaken there is a difference between when using firstElementChild versus using firstChild. The first property finds “only” Element objects. The second property finds any Nodes regardless of their nodeType. The author first looks for an Element object. Failing that his else loop only breaks when he reaches the end of the children or when the loop finds the firstChild that is an element(nodeType==1). Why if he has already determined that there is no firstElementChild would he look for it again in an else loop? It doesn’t make sense.

The author desired to get the first child element. Some web browsers have a nice way of doing that with the firstElementChild method.

This is where you seem to get hung up though. If firstElementChild is not found, that does not necessarily mean that there is no child element to be found. That can also occur when the web browser completely fails to support the firstElementChild method.

Time for some details. This W3C DOM Compatibility - Traversal page shows that the Firefox 3.0 and IE up to IE8 web browsers do not support the firstElementChild method. So even if there is a child element to be found, those browsers will just completely fail because they do not know what the firstElementChild is.

It is for those older web browsers that the else case is used.


if (test for browser compatibility) {
    use browser-supported method
} else {
    use compatibility code to achieve same end-result
}

You can see a similar example of compatibility code on the Array forEach documention page