Course: JavaScript

Progress (0%)

  1. Foundation

  2. Numbers

  3. Strings

  4. Conditions

  5. Loops

  6. Arrays

  7. Functions

  8. Objects

  9. Exceptions

  10. HTML DOM

  11. CSSOM

  12. Events

  13. Drag and Drop

  14. opt Touch Events

  15. Misc

  16. Project: Analog Clock

HTML DOM - Selecting Elements

Chapter 48 41 mins

Learning outcomes:

  1. Selecting elements by their ID โ€” getElementById()
  2. Selecting elements by their class name โ€” getElementsByClassName()
  3. Selecting elements by their tag name โ€” getElementsByTagName()
  4. Mixing the aforementioned methods to select elements
  5. Selecting elements via CSS selectors โ€” querySelector() and querySelectorAll()
  6. Pre-selected elements
  7. Selecting elements via traversal

First select the element..

Before performing any DOM methods on any HTML element, it is necessary that we first select it. Selecting an element in the DOM is just like a key for a car โ€” we can't start the car without the key!

When we select an element, we basically fetch it in the form of a JavaScript DOM object on which we can access a huge variety of properties and methods as we wish. We'll explore most of these in the next HTML DOM โ€” HTML Elements chapter.

In this chapter, we'll introduce ourselves to an array of ways to select a single element or a bunch of elements, that are dependent on either their attribute values, tag names or parent-child relationships.

Let's begin!

Selecting by ID

Perhaps, the easiest way to select an HTML element in JavaScript is using the getElementById() method of the document object.

The getElementById() method, of the document object, simply selects an element based on its HTML id attribute.

The ID of the desired element is passed to the method as a string, which then looks for it and finally returns a reference to its node, if it exists, or else the value null.

Let's see a quick example.

Consider the following <p> element with an id of "p1":

<p id="p1">A paragraph</p>

To select this paragraph, based on its id, we can simply write the following:

var paraElement = document.getElementById('p1');

The expression document.getElementById('p1') here would return an HTMLParagraphElement instance for the <p> element shown above, which consequently gets saved in the variable paraElement.

Let's inspect the prototype object of paraElement:

Object.getPrototypeOf(paraElement)
HTMLParagraphElementย {...}

As is evident, paraElement is an instance of HTMLParagraphElement.

If you're using Google Chrome, go to the console, write the statement above, and then take your mouse over the returned text, which is a piece of HTML code. The respective element will be highlighted in the HTML document.

Once we have an element node in hand, we can do a lot of different things with it.

Just to name one thing: we can retrieve the content of the element by accessing the property innerHTML:

var paraElement = document.getElementById('p1');

// Log the element's content.
console.log(paraElement.innerHTML);
A paragraph
We will explore the innerHTML property, in detail, in the next HTML DOM โ€” HTML Elements chapter.

IDs in a web page are supposed to be unique and so accordingly, the method getElementById() returns only a single element.

If we have two elements on a web page with similar ids, getElementById() will only return the first element.

Below shown is an example:

<p id="p1">Paragraph 1</p>
<p id="p1">Paragraph 2</p>
var paraElement = document.getElementById('p1');

console.log(paraElement.innerHTML);
Paragraph 1

As you can clearly see from the log, the selected element is the first <p> element with the given ID.

Another important thing to note about getElementById(), and some other methods in the following sections, is that the argument provided to it, which is the value of an HTML id attribute, is treated case-insensitively.

That is, it doesn't matter what is the case of the actual id attribute in the HTML source code and the value provided to getElementById() โ€” they both are matched ignoring the casing.

Let's take an example.

Here's the same HTML as before:

<p id="p1">A paragraph</p>

Now, let's retrieve this <p> element by calling document.getElementById() with the uppercase string 'P1', and the log its innerHTML:

var paraElement = document.getElementById('P1');

console.log(paraElement.innerHTML);
A paragraph

Clearly, the element does indeed get selected, even though the ID argument provided to getElementById() is in uppercase, whereas the corresponding id attribute in the HTML source code is in lowercase.

Selecting by class

Apart from id, another HTML attribute used frequently to help in selecting given elements, in CSS and JavaScript, is the class attribute.

To fetch a bunch of elements with a given class name, we can use the getElementsByClassName() method.

The getElementsByClassName() method, of the document object, selects a collection of elements based on a given HTML class attribute.

Like getElementById(), this method takes in the given class name as a string.

However, what's different is that getElementsByClassName() returns an HTMLCollection instance containing all the elements with the given class name; not just a single element.

Notice the 's' in getElementsByClassName. The name implies that the method considers to select elements, not just a single element!

Let's see what exactly is an HTMLCollection.

What is an HTMLCollection?

An HTMLCollection instance is, more or less, like a JavaScript array.

That is, it is a sequence of elements, each of which can be accessed individually via bracket notation and where the total number of elements is denoted by the length property.

However, note that we only say that an HTMLCollection object is 'like' an array โ€” it's NOT actually an array. The HTMLCollection class doesn't inherit from Array, and so doesn't have access to any array methods like indexOf(), slice(), push(), etc.

Following the fact that getElementsByClassName() returns back an HTMLCollection object containing all the selected elements, we have to first individually access a particular element in this collection before we can work with it.

Let's see a quick example to help us understand everything much better.

In the HTML code shown below, we have two <p> elements with a class of p1:

<p class="p1">A .p1 element</p>
<p class="p1">Another .p1 element</p>

To select both these .p1 elements, we write the following statement:

var paraElements = document.getElementsByClassName('p1');

With the invocation of the method, all the elements in the DOM tree with the HTML class p1 are searched for and then placed inside an HTMLCollection list which is returned at the end and saved in the variable paraElements.

Notice the naming of the variable that holds the returned HTMLCollection instance here. We call it paraElements (i.e. it's plural) since it is a list of all paragraph elements in the document. Another good name could be paraElementList.

Anyways, with both the .p1 elements selected, now if we want to use either of the selected elements for further processing, we have to use an index โ€” just like we do with arrays.

For instance, if we want to select the first .p1 element, we'd write paraElements[0].

In the code below, we log the innerHTML of both the .p1 elements in the HTML source code above:

var paraElements = document.getElementsByClassName('p1');

console.log(paraElements[0].innerHTML);
console.log(paraElements[1].innerHTML);
A .p1 element
Another .p1 element

paraElements[0] refers to the first element in the paraElements collection; similarly, paraElements[1] refers to the second element in the collection.

Simple.

In the example above, since there were only two <p> elements, we managed to refer to each one of them manually. However, in general, when the elements are in large numbers, it's common, convenient and efficient to shift this logic under the hood of loops.

Working with an HTMLCollection object in a for loop is extremely easy โ€” thanks to its length property.

Below shown is the rewritten version of the code above, using a for loop:

var paraElements = document.getElementsByClassName('p1');

for (var i = 0, len = paraElements.length; i < len; i++) {
   console.log(paraElements[i].innerHTML);
}
A .p1 element
Another .p1 element

Once again, simple. Wasn't this?

Moving on, as with getElementById(), the argument sent to getElementsByClassName() is also treated case-insensitively. That is, if we call getElementsByClassName('p1') or getElementsByClassName('P1'), they are both effectively the same.

One final thing left to be discovered in regards to getElementsByClassName() is that the HTMLCollection instance it returns is a live collection.

What exactly does this mean is covered in the following snippet:

What is a live HTMLCollection?

The most interesting aspect of the HTMLCollection class is that it is used to represent live collections of elements.

That is, if we have an HTMLCollection instance with us at a given point of time, and then later on, modify the DOM by adding or removing any particular set of elements, the HTMLCollection instance would automatically get updated. We won't have to again retrieve a new HTMLCollection.

Let's illustrate this with an example.

Consider the following HTML code:

<p class="p1">A .p1 element</p>
<p class="p1">Another .p1 element</p>

And now consider the following JavaScript:

var paraElements = document.getElementsByClassName('p1');

console.log(paraElements.length);

// Add a new .p1 element to the end of <body>
var paraElement = document.createElement('p');
paraElement.className = 'p1';
document.body.appendChild(paraElement);

console.log(paraElements.length);
2 3

At the start, we call getElementsByClassName() to select all .p1 elements in the document. This obviously returns back an HTMLCollection instance, with the two elements shown in the HTML code above. Then we log the length of this collection, which is simply 2.

Next up, we create another .p1 element, this time using JavaScript, in lines 6 - 8. We'll understand how to create elements using DOM methods in the next chapter.

Anyways, with the third element added to the document, by the execution of line 8, we finally log the length of the initial collection of .p1 elements again. At this point, the value logged is 3.

We didn't make even a single change to paraElements in the code, yet it was able to recognize the addition of a new .p1 element into the DOM tree, and consequently update itself.

This nature of HTMLCollection means that we don't have to do create a new HTMLCollection instance in order to get an updated version of a given collection of elements โ€” the initial HTMLCollection instance would automatically get updated.

For example, in the code below, the highlighted line is meaningless:

var paraElements = document.getElementsByClassName('p1');

console.log(paraElements.length);

// Add a new .p1 element to the end of <body>
var paraElement = document.createElement('p');
paraElement.className = 'p1';
document.body.appendChild(paraElement);

// No need to recompute an HTMLCollection.
paraElements = document.getElementsByClassName('p1'); console.log(paraElements.length);

Simple.

This nature of HTMLCollection also means that we should cache the length of an HTMLCollection instance if we want to iterate over all its elements.

For example, consider the code below:

var paraElements = document.getElementsByClassName('p1');

for (var i = 0; i < paraElements.length; i++) {
   console.log(paraElements[i].innerHTML);
}

Notice the loop's condition here โ€” it accesses the length property of paraElements each time it's evaluated. This, although won't cause a noticeable performance glitch, has a certain amount of overhead associated with it (which we'll cover up next in this section).

A slightly better alternative is to cache the length of the HTMLCollection in a variable, and then use that variable in the loop's condition. In this way, each time the condition is evaluated, we would only be accessing a fixed-valued variable.

Here's a better version of the code above:

var paraElements = document.getElementsByClassName('p1');

for (var i = 0, len = paraElements.length; i < len; i++) {
   console.log(paraElements[i].innerHTML);
}

So now that we know that all HTMLCollections are live containers for given elements, there is another similar question that comes to the mind: how is a live collection implemented by engines?

Hmm. This sure is a good question.

The thing is that every engine has its own rules for implementing live collections, but at the core there is one common and extremely simple idea used. It is discussed as follows:

How are live collections implemented?

All properties/methods of the HTMLCollection class are internally configured to search for the underlying set of elements, each time they are accessed.

For example, whenever we access the length property of an HTMLCollection instance, it recomputes the set of elements using the DOM tree available at that time and then returns the length of that set.

As another example, even accessing a given element from an HTMLCollection instance, using bracket notation (for example, paraElements[0]) would recompute the set of elements by searching the latest DOM tree.

This approach, however, would have had a negative impact on performance in cases when the DOM tree remains the same throughout the execution of a given piece of JavaScript code working with an HTMLCollection instance.

And that's where the idea of caching comes in.

To state is precisely, all properties/methods of the HTMLCollection class are configured to search for the underlying set of elements each time they are accessed, given that the internal cache of the respective instance has become invalidated.

So how does an internal cache for an HTMLCollection instance become invalidated?

Well, whenever the DOM tree is modified, the internal cache of an HTMLCollection instance gets invalidated. Thereafter, accessing any property/method on the HTMLCollection instance would lead to a new search over the DOM tree for the underlying set of elements.

However, if the DOM tree is not modified since the creation of an HTMLCollection instance, its internal cache would remain valid and so likewise, accessing any property/method on it would not lead to any recomputations.

Easy, wasn't this?

Time to test your understanding of getElementsByClassName().

Write a program that selects all the .hidden elements, as shown below, and then logs their innerHTML properties one-by-one.

<section class="hidden">Some content</p>

<p>A <span class="hidden">span</span> element</p>

<div class="hidden">Some content</div>
<div>Some more content</div>
var hiddenElements = document.getElementsByClassName('hidden');

for (var i = 0, len = hiddenElements.length; i < len; i++) {
   console.log(hiddenElements[i].innerHTML);
}

Selecting by tag name

Say we have no id or class attributes on a given set of HTML elements and still want to select them based on their tagnames.

Well, it ain't a big deal โ€” we just ought to use the getElementsByTagName() method.

The getElementsByTagName() method, of the document object, selects a collection of elements based on a given HTML tag name.

It operates exactly like getElementsByClassName(), except for that it searches for elements based on given tag names (such as <p>, <div>, <span>) rather than given class names.

The tag name is provided to getElementsByTagName(), once again, as a string, which then returns back an HTMLCollection containing all the matched elements.

For example, to select all <p> elements, we would call document.getElementsByTagName('p').

As always, it's time for an example.

In the code below, we select all the <p> elements shown, and then log each one's innerHTML one-by-one:

<p>First paragraph</p>
<p>Second paragraph</p>

<div>A div element</div>

<p>Third paragraph</p>
var paraElements = document.getElementsByTagName('p');

for (var i = 0, len = paraElements.length; i < len; i++) {
   console.log(paraElements[i].innerHTML);
}
First paragraph
Second paragraph
Third paragraph

As you might agree, the code is pretty straightforward to follow along.

Remember that getElementsByTagName() also returns a collection, just like getElementsByClassName(). Hence, to refer to an individual element, we have to use its index.

Moving on, as with both the methods discussed above, i.e. getElementById() and getElementsByClassName(), the argument passed to getElementsByTagName() is treated case-insensitively.

Hence, both getElementsByTagName('script') and getElementsByTagName('SCRIPT') are identical to one another.

Write a program that selects all the <section> elements shown below, and then logs their innerHTML property one-by-one.

<h1>Heading 1</h1>
<section>Section 1</section>

<h1>Heading 2</h1>
<section>Section 2</section>
var sectionElements = document.getElementsByTagName('section');

for (var i = 0, len = sectionElements.length; i < len; i++) {
   console.log(sectionElements[i].innerHTML);
}
Section 1 Section 2

Mixing these together

The beauty of all the three element selection methods we saw above is that they not just operate on the document object, but also on a given element node.

Speaking technically, all the three methods, i.e. getElementById(), getElementsByClassName() and getElementsByTagName(), are defined on the Document class and additionally on the Element class as well.

Not surprisingly, the methods of the Document class perform search for given elements over the entire document. In contrast, the methods of the Element class perform search only within the given Element instance.

In all of the examples above, we called the methods on the document object, in which case the searching was performed over the entire HTML document.

Now in this section, we shall consider these methods on given element nodes as well, and in this way mix them together in complex ways to select nested sets of elements.

Starting with our very first example, consider the HTML below:

<div id="main">
   <p class="small">A paragraph inside</p>
   <p class="small">Another paragraph inside</p>
</div>

<p class="small">A paragraph outside</p>

We have a #main container with two .small elements inside it, in addition to a .small element outside it.

Our job is to select the two .small elements inside #main.

First thing's first, we'll start by selecting the #main element using getElementById() on document:

var mainElement = document.getElementById('main');

Now, since searching has to be done within this #main element, we'll call the second selection method on it. In particular, we'll invoke getElementsByClassName() on mainElement in order to select both the .small elements inside it.

This is accomplished in line 2 below:

var mainElement = document.getElementById('main');
var smallElements = mainElement.getElementsByClassName('small');

Now, let's iterate over smallElements and log the innerHTML of each of its items:

var mainElement = document.getElementById('main');
var smallElements = mainElement.getElementsByClassName('small');

for (var i = 0, len = smallElements.length; i < len; i++) {
   console.log(smallElements[i].innerHTML);
}
A paragraph inside
Another paragraph inside

As evident by the logs, smallElements holds the two <p> elements inside the #main container.

Amazing!

Let's now play with another example, this time a bit more complicated then the one above.

In the snippet below, we have various .small elements scattered over the HTML markup. The goal is to log the content of all those that are contained within .main elements:

<div class="main">
   <p class="small">Paragraph 1</p>
   <p class="small">Paragraph 2</p>
</div>

<p class="small">Paragraph 3</p>

<div class="main">
   <p class="small">Paragraph 4</p>
   <p class="small">Paragraph 5</p>
</div>

We'll start by getting done with the first concern, i.e selecting all .main elements:

var mainElements = document.getElementsByClassName('main');

After this, for each selected .main element, we'll select all .small elements within it and then log their content one-by-one:

var mainElements = document.getElementsByClassName('main');

for (var i = 0, len = mainElements.length; i < len; i++) {

   // Select all .small elements inside the current .main element.
   var smallElements = mainElements[i].getElementsByClassName('small');

   for (var j = 0, len2 = smallElements.length; j < len2; j++) {
      console.log(smallElements[j].innerHTML);
   }
}

Notice the nested for loops โ€” the first one works on each selected .main element, whereas the second one works on each selected .small element. Simple.

Here's the output produced by this code:

Paragraph 1
Paragraph 2
Paragraph 4
Paragraph 5

Voila! We were successful in selecting the desired set of elements.

Now although, the code above does select the desired elements, you would surely agree that it's way too complex for even simple element selection problems.

Imagine having to select all <b> elements inside all <div> elements inside all .small elements inside all .main elements, using just the three methods discussed above. We'll need 4 for loops iterating over the desired sets of elements, starting with .main, and filtering out as we go deeper.

Clearly, this would be way too complex.

A better method is needed here, and indeed there is. In fact, there are two such methods โ€” querySelector() and querySelectorAll(), as we shall see in the next section.

Selecting by CSS selector

As we just saw above, selecting given elements within given elements in JavaScript, solely using the methods getElementById(), getElementsByClassName() and getElementsByTagName(), can quickly become a cumbersome task.

Fortunately, there's a much better way to use instead. And that is to employ the CSS selector syntax to select given elements with the help of the querySelector() and the querySelectorAll() methods.

For more info on the CSS selector syntax, please refer to CSS Selectors.
The querySelector() and the querySelectorAll() methods, of the document object, select elements based on a given CSS selector.

The CSS selector is passed in to the methods as the first argument, in the form of a string, just how we do in all of the selection methods that we've seen thus far.

The difference between these two methods is that querySelector() selects the first matched element while querySelectorAll() selects all the matched elements.

As with getElementsByClassName() and getElementsByTagName(), the method querySelectorAll() also returns back a collection containing all the matched elements, however this collection is NOT an HTMLCollection instance.

Rather it is a NodeList instance.

What is a NodeList?

A NodeList object is very similar to an HTMLCollection object.

It also supports the usage of bracket notation to access individual elements, and has a length property containing the total number of elements.

However, one major difference between NodeList and HTMLCollection is that the former is meant to represent a collection of arbitrary nodes, not just element nodes. HTMLCollection, as we know, is only meant to represent a collection of element nodes.

But there is a confusion here.

The querySelectorAll() method selects only HTML elements. This means that a much more sensible class to be implemented by it would be HTMLCollection, instead of NodeList.

So then why does querySelectorAll() return a NodeList instance?

As expected, it is also indexed, has a length property and contains elements accessible via bracket notation.

On the outskirts, it operates exactly like an HTMLCollection object, however it has one crucial difference. This we shall explore later in the section below.

Anyways, using these two methods, we can easily and efficiently select elements with extremely complex nestings; which would otherwise take multiple lines of code if done using the methods discussed above.

Let's consider the same example above, this time to be solved in a more elegant way:

<div class="main">
   <p class="small">Paragraph 1</p>
   <p class="small">Paragraph 2</p>
</div>

<p class="small">Paragraph 3</p>

<div class="main">
   <p class="small">Paragraph 4</p>
   <p class="small">Paragraph 5</p>
</div>

We needed to select all .small elements that were within a .main element and log each one's innerHTML.

Following we use the querySelectorAll() method to solve this task:

var eleList = document.querySelectorAll(".main .small");

for (var i = 0, len = eleList.length; i < len; i++) {
   console.log(eleList[i].innerHTML);
}
Paragraph 1
Paragraph 2
Paragraph 4
Paragraph 5

If we use querySelector() rather than querySelectorAll() here, it would return only the first matched element rather than a list.

Consider the code below:

var ele = document.querySelector(".main .small");

console.log(ele.innerHTML);
Paragraph 1
You can use any CSS selectors in these methods like querySelector(".article:last-child").
querySelector(selector) is same as calling querySelectorAll(selector)[0].

Pre-defined DOM collections

Apart from the methods we saw above to select elements, JavaScript comes equipped with a handful of properties on the document object that have some common HTML elements pre-selected.

Below shown is the list of these properties:

  1. document.documentElement selects the <html> element.
  2. document.head selects the <head> element.
  3. document.body selects the <body> element.
  4. document.scripts selects all <script> elements.
  5. document.links selects all the <a> elements.
  6. document.forms selects all the <form> elements.
  7. document.images selects all the <img> elements.

So, in order to select the <body> element, instead of going like:

document.getElementsByTagName("body")[0];

we can simply just write:

document.body

And in this way we can select the <body> element pretty easily.

What will the following expression evaluate to?

document.getElementsByTagName("script")[0] === document.scripts
  • true
  • false

In the coming chapters we'll be, at least, using the first three properties pretty frequently โ€” document.documentElement, document.head, and document.body. So, just make sure you have a good understanding of them.

Relationship-based fetching

In the previous HTML DOM Tree chapter we saw the DOM tree model and how various nodes are linked together via parent-child or sibling relationships.

For instance, <html> is the parent node of <body>, while <body> is the child node of <html>. Similarly, <head> is the sibling of <body> and so on and so forth.

Now, in this chapter, we shall use this relationship to select given elements in a document starting from another given element.

The properties we'll be exploring are parentNode, childNodes, nextSibling, previousSibling and after these children, nextElementSibling and previousElementSibling.

parentNode

The parentNode property of a given element node points to its parent.

If an element does not have a parent node, like the document object, this property will be equal to null.

For example, the parentNode property on the document.body element will point to the document.documentElement object.

Consider the code below, where we justify this relationship:

document.body.parentNode === document.documentElement; // true

childNodes

The childNodes property of a given element node returns a list of all its child nodes.

More specifically, childNodes returns a NodeList object containing all the children of a given node.

Can you recall which other property/method returns a NodeList object?

Remember that the children can also be text and comment nodes, in addition to element nodes.

Consider the HTML below:

<html>
<head>
   <title>HTML DOM</title>
</head>
<body>
   <p>Experimenting with the DOM.</p>
</body>
</html>

The <html> element here has five child nodes, in the following order:

  1. A text node
  2. The <head> element node
  3. A text node
  4. The <body> element node
  5. A text node

Now you may be thinking where are the three text nodes coming from.

If you notice, there are newlines and indentations after the <html>, </head> and </body> tags. These are text nodes that happen to be the children of the <html> element.

Anyways, inspecting the childNodes property of the documentElement object, we see that it returns exactly the result were expecting:

var children = document.documentElement.childNodes;

console.log(children.length); // 5
console.log();

for (var i = 0, len = children.length; i < len; i++) {
   console.log(children[i].nodeName);
}

The nodeName property of a text node returns the string #text, whereas the one for an element node returns its tagname (uppercased).

5
#text
HEAD
#text
BODY
#text

As expected, we get five children โ€” with three text and two element nodes.

If we remove all these text nodes from the markup, we would end up with only two child nodes of the <html> element, as shown below:

<html><head><title>HTML DOM</title></head><body><p>Experimenting with the DOM.</p></body></html>
var children = document.documentElement.childNodes;

console.log(children.length); // 2
console.log();

for (var i = 0, len = children.length; i < len; i++) {
   console.log(children[i].nodeName);
}
2
HEAD
BODY

Simple as a piece of cake!

previousSibling

As we know from the previous chapter, siblings are nodes with the same parent.

Following from this, the previousSibling property of a given node points to the sibling that comes immediately before it in the HTML markup, or else the value null.

Consider the following code:

<html>
<head><title>HTML DOM</title></head><body>Hello.</body>
</html>

As is obvious, the previous sibling of <head> is a text node, while the previous sibling of <body> is the <head> element.

Let's now see what does the previousSibling property return for each of these elements:

var prevSib = document.head.previousSibling;
console.log(prevSib.nodeName);

prevSib = document.body.previousSibling;
console.log(prevSib.nodeName);
#text
HEAD

As expected, first we get a text node and then the <head> element returned.

nextSibling

Now that you've understood previousSibling, understanding the logic of nextSibling won't be a problem.

For a given node, its nextSibling property points to the sibling that comes immediately after it.

Let's apply this to the same code above:

<html>
<head><title>HTML DOM</title></head><body>Hello.</body>
</html>

The next sibling of <head> is <body>, while the next sibling of <body> is a text node.

As before, let's also check this through code:

var nextSib = document.head.nextSibling;
console.log(nextSib.nodeName);

nextSib = document.body.nextSibling;
console.log(nextSib.nodeName);
BODY
#text

Once again, just what we expected.

Selecting elements only

Often times, while selecting nodes via the traversal properties we saw above, one needs to make sure that the properties return elements only.

People are generally not concerned with other types of nodes. Who would want to process text or comment nodes anyway?

However, as we know, the properties childNodes, previousSibling and nextSibling can all point to non-element nodes โ€” and so to select elements only, we are just left with manual checks.

Fortunately, JavaScript comes with three special properties that operate exactly like childNodes, previousSibling and nextSibling, but that take into account elements only.

They are children, previousElementSibling and nextElementSibling, respectively.

As the names might suggest, these properties are meant to select only element nodes.

Let's see each one...

children

The children property of a given element node returns an HTMLCollection object containing all its children element nodes.

Recall that childNodes returns a NodeList object. This is a clear indicator of the fact that it returns a list with non-element nodes as well.

Consider the code below:

<html>
<head>
   <title>HTML DOM</title>
</head>
<body>
   <p>Experimenting with the DOM.</p>
</body>
</html>

As we have seen above, calling childNodes on documentElement returns a list of five nodes. However, calling children will return only two nodes; both elements:

var childrenElements = document.documentElement.children;

console.log(childrenElements.length); // 2
console.log();

for (var i = 0, len = childrenElements.length; i < len; i++) {
   console.log(childrenElements[i].nodeName);
}
2
HEAD
BODY

previousElementSibling

The previousElementSibling property, of a given element node, returns the sibling element immediately coming before it.

If there isn't any such element, the property returns the value null.

Below shown is an illustration:

<html>
<head>
   <title>HTML DOM</title>
</head>
<body>
   <p>Experimenting with the DOM.</p>
</body>
</html>
var prevElemSib = document.head.previousElementSibling;
console.log(prevElemSib);

prevElemSib = document.body.previousSibling;
console.log(prevElemSib.nodeName);

The previous element sibling of <head> is null, whereas the previous element sibling of <body> is <head>.

null
HEAD

Just remember that previousElementSibling is meant to return element nodes only; whereas previousSibling is meant to return non-element nodes, as well.

nextElementSibling

The nextElementSibling property, of a given element node, returns the sibling element coming immediately after it.

If there isn't any such element, it returns null.

Below shown is an illustration:

<html>
<head>
   <title>HTML DOM</title>
</head>
<body>
   <p>Experimenting with the DOM.</p>
</body>
</html>
var nextElemSib = document.head.nextElementSibling;
console.log(nextElemSib);

nextElemSib = document.body.previousSibling;
console.log(nextElemSib.nodeName);

The next element sibling of <head> is <body>, whereas the next element sibling of <body> is null.

BODY
null

Once again remember that nextElementSibling is meant to return element nodes only; whereas nextSibling is meant to return non-element nodes, as well.

And this is all that we need to perfect in the HTML DOM selection world.

In conclusion

Just like we said before, selecting an element is vital and a must, before being able to play with the potential of the DOM.

Once you've perfected how to select given stuff in an HTML document and then process it one-by-one, in the case of lists, you can consider moving on to explore how to work with HTML DOM elements.

Surely, a fun avenue awaits!