Understanding the DOM Tree in JavaScript

Starting from this lesson, we are going to move on to the more practical uses of JavaScript. To begin with, let's talk about how the language works in the frontend part of a web application.

Frontend is the part of the web application that is displayed to the user, meaning your JavaScript code will be embedded into the webpage, and it will run on top of the browser environment, instead of Node.js.

There are two ways to embed JavaScript code. You can either use in-page JavaScript like this:

html
1<!doctype html>
2<html lang="en">
3  <head>
4    <meta charset="UTF-8" />
5    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6    <title>Document</title>
7  </head>
8
9  <body>
10    <p>Lorem ipsum dolor. . .</p>
11    <p>. . .</p>
12
13    <script>
14      // Your JavaScript code here
15    </script>
16  </body>
17</html>

Or put your JavaScript code in a separate file:

javascript
1.
2├── index.html
3└── index.js

And then import it into your HTML document.

html
1<!doctype html>
2<html lang="en">
3  <head>
4    <meta charset="UTF-8" />
5    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6    <title>Document</title>
7  </head>
8
9  <body>
10    <button onclick="example()">Click Me</button>
11
12    <script src="index.js"></script>
13  </body>
14</html>

In most cases, the second method is recommended. As your project grows, putting everything in a single file will make future maintenance a nightmare.

The JavaScript file can be imported similar to how we can load images, except you must use the <script> tag.

It is best to load JavaScript right before the closing <body> tag, so that it does not block the rendering of the rest of the page.

As an example, put the following code into your .js file.

javascript
1function example() {
2  alert("JavaScript file imported successfully.");
3}

In this case, the index.js contains an example() function, which executes alert(). alert() is a built-in function in the browser environment (as opposed to the Node.js environment we've been working in), which creates a pop up alert box.

alert box

Inside the index.html file, notice that we have a <button> element with an onclick attribute. This attribute is an event handler for the "click" action, meaning when the button is clicked, the function example() will be executed, and the alert box should pop up.

What is the DOM tree

When the browser renders a web page, it will parse the HTML document and create a DOM tree. DOM stands for Document Object Model. For example, given the following HTML document:

html
1<!doctype html>
2<html lang="en">
3  <head>
4    <meta charset="UTF-8" />
5    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6    <title>Document</title>
7  </head>
8
9  <body>
10    <p>
11      Lorem ipsum dolor, sit amet consectetur adipisicing elit. Repellendus
12      veniam, vitae aliquid amet aut dolorem dolor blanditiis explicabo
13      perspiciatis culpa rerum deleniti, fugit minima reprehenderit magni odit
14      unde aperiam. Cumque.
15    </p>
16    <p>
17      Ipsum enim molestiae sequi explicabo, soluta laudantium consequatur ipsa
18      aperiam numquam. Quas eius officiis ducimus alias aut labore, excepturi
19      saepe expedita adipisci.
20    </p>
21  </body>
22</html>

The browser will create a tree structure which looks like this:

The DOM tree

Each node is represented by an object, allowing you to access its content, attributes, styles, etc.

Notice that the textual content is considered a separate node from its containing element. Every node object has a nodeType property that allows you to check the type of that node.

For example, it can be an element node (represented by the number 1), a text node (represented by the number 3), or a comment node (represented by the number 8).

javascript
1console.log(document.body.nodeType);
text
11

These nodes are also interconnected, which means you can move up and down on the DOM tree, and access any node you want.

The node objects can be accessed through the document variable, which is a global variable in the browser environment. For example, document.documentElement refers to the <html> element in the HTML page.

javascript
1console.log(document.documentElement);

print the html element

Hint: We are working in the browser environment now, not Node.js, so check the logs in the browser's developer tools.

Selecting elements

Under the document object, there are several methods for selecting elements based on their name, class, id, or the element tag.

  • getElementById()

The getElementById() allows you to select an element based on its id. For example,

html
1<body>
2  <p id="p1">Lorem ipsum dolor. . .</p>
3  <p id="p2">. . .</p>
4
5  <script src="index.js"></script>
6</body>
javascript
1let p1 = document.getElementById("p1");
2
3console.log(p1);

select element by id

After selecting the element, you can then access its content using the innerHTML property.

javascript
1console.log(p1.innerHTML);

innerHTML

We'll talk more about how to manipulate HTML elements in the next lesson. For now, let's focus on selecting elements.

  • getElementsByClassName()

You can also select elements based on their class. For example,

html
1<body>
2  <p class="blue">Lorem ipsum dolor. . .</p>
3  <p class="blue">. . .</p>
4
5  <script src="index.js"></script>
6</body>
javascript
1let blue = document.getElementsByClassName("blue");
2
3console.log(blue);

select elements by class

And because there could be multiple elements under one class, this method will return a collection of node objects. This collection has an array-like structure, which means you can access each individual element using a for of loop:

javascript
1let blue = document.getElementsByClassName("blue");
2
3for (let e of blue) {
4  console.log(e);
5}

Accessing each HTML node

  • getElementsByTagName() and getElementsByName()

These two methods are similar to getElementsByClassName(), except getElementsByTagName() collects all elements with the specified tag, and getElementsByName() collects all elements with the specified name.

getElementsByTagName()

html
1<body>
2  <p>Lorem ipsum dolor. . .</p>
3  <p>. . .</p>
4
5  <script src="index.js"></script>
6</body>
javascript
1let p = document.getElementsByTagName("p");
2
3for (let e of p) {
4  console.log(e);
5}

getElementsByName()

html
1<body>
2  <form action="/">
3    <label for="name">Name:</label>
4    <input type="text" name="name" id="name" />
5
6    <label for="email">Email:</label>
7    <input type="email" name="email" id="email" />
8
9    <input type="submit" value="Submit" />
10  </form>
11
12  <script src="index.js"></script>
13</body>
javascript
1let emailInput = document.getElementsByName("email");
2
3for (let e of emailInput) {
4  console.log(e);
5}

Both of them return a collection of HTML node objects, which you can iterate over using the for of loop.

  • querySelector() and querySelectorAll()

Lastly, you can also select elements using query selectors, which we've discussed in the CSS selector lesson.

The querySelector() method returns the first element in the DOM tree that matches the given query selector.

html
1<body>
2  <p class="underlined">Lorem ipsum dolor. . .</p>
3  <p class="underlined">. . .</p>
4
5  <script src="index.js"></script>
6</body>
javascript
1let underlined = document.querySelector(".underlined");
2
3console.log(underlined);

query select one

And the querySelectorAll() method selects all the elements that the selector matches. Again, it returns a collection of elements that you can iterate over using a loop.

javascript
1let underlined = document.querySelectorAll(".underlined");
2
3for (let e of underlined) {
4  console.log(e);
5}

query select all

There is one small difference between querySelectorAll() and getElementBy*() methods, when it comes to iterating over the returned results.

The getElementBy*() methods return an HTMLCollection, meaning you can only iterate over them using loops. But querySelectorAll() returns a NodeList, which supports the use of forEach(). For example,

javascript
1let underlined = document.querySelectorAll(".underlined");
2
3underlined.forEach((e) => {
4  console.log(e);
5});

This should give you the same result as before.

Moving through the DOM tree

For every node object, there exist several properties that allow you to move to other nodes:

  • parentNode and parentElement
  • childNodes and children
  • firstChild and firstElementChild
  • lastChild and lastElementChild
  • nextSibling and nextElementSibling
  • previousSibling and previousElementSibling

Their relations are shown in the diagram below.

moving through the DOM tree

Nodes vs. elements

We made a small mistake at the beginning of this lesson. Our DOM tree diagram was incomplete.

We only included the element nodes and the text nodes that contain information, but in fact, there are also text nodes representing newline characters (\n) and spaces in between the elements. You can see them by executing the following code:

javascript
1console.log(document.body.childNodes);

The childNodes property returns all child nodes of <body>.

child nodes of body

They were omitted for simplicity in our previous DOM tree diagram. But when moving through the DOM tree, you need to remember that they exist, or you might end up going to the wrong node.

The hidden text nodes

In fact, there are 12 different types of nodes in a DOM tree, but in most cases, we only work with element nodes and the text nodes that contain information.

This can be very annoying when moving through the DOM tree. Why do we have to count the nodes that don't really show up in the browser? Luckily, there is an Element interface that allows you to move through the tree while only counting the element nodes, as we will see later.

Getting the parent

Let us start with the parent. For example,

html
1<body>
2  <div id="parent">
3    <p id="child">Lorem ipsum dolor. . .</p>
4
5    <p>. . .</p>
6  </div>
7
8  <script src="index.js"></script>
9</body>

Starting from #child, you can access the parent node using the property parentNode.

javascript
1let child = document.getElementById("child");
2let parent = child.parentNode;
3
4console.log(child);
5console.log(parent);

This property is more general-purposed, as it works for all node types. If you want something specific to element nodes, you can go with parentElement.

javascript
1let child = document.getElementById("child");
2let parent = child.parentElement;
3
4console.log(child);
5console.log(parent);

Getting the child

html
1<body>
2  <div id="parent">
3    <p>Lorem ipsum dolor. . .</p>
4
5    <p>. . .</p>
6  </div>
7
8  <script src="index.js"></script>
9</body>

To access the children of an element, use the property childNodes.

javascript
1let parent = document.getElementById("parent");
2
3let children = parent.childNodes;
4
5console.log(children);

child nodes

Individual nodes can be accessed using a for of loop.

javascript
1let parent = document.getElementById("parent");
2
3let children = parent.childNodes;
4
5for (let e of children) {
6  console.log(e);
7}

Or by specifying an index number directly.

javascript
1let parent = document.getElementById("parent");
2
3let children = parent.childNodes;
4
5console.log(children[0]);
6console.log(children[1]);
7console.log(children[2]);
8console.log(children[3]);
9console.log(children[4]);

If you only wish to work with element nodes, use children instead.

javascript
1let parent = document.getElementById("parent");
2
3let children = parent.children;
4
5console.log(children);

child elements

Similarly, individual element nodes are accessed with an index or a for of loop.

Sometimes, you might only care about an element's first or last child nodes. For example, you could be trying to add a notification banner at the top of a webpage, and you need to locate the first child of the <body> element. In this case, several shortcut properties are provided.

To access the first or last child node of an element, use firstChild or lastChild.

html
1<body>
2  <div id="parent">
3    <p id="first">Lorem ipsum dolor. . .</p>
4
5    <p id="second">. . .</p>
6
7    <p id="third">. . .</p>
8  </div>
9
10  <script src="index.js"></script>
11</body>
javascript
1let parent = document.getElementById("parent");
2
3console.log(parent.firstChild);
4console.log(parent.lastChild);

first and last child node

To access the first or last element child node, use firstElementChild or lastElementChild.

javascript
1let parent = document.getElementById("parent");
2
3console.log(parent.firstElementChild);
4console.log(parent.lastElementChild);

first and last child element

Getting the siblings

The sibling nodes are accessed through properties nextSibling and previousSibling.

javascript
1let middle = document.getElementById("second");
2
3console.log(middle.previousSibling);
4console.log(middle.nextSibling);

In most cases, you are going to get the line break nodes, which are not very useful. Instead, you can exclude them by accessing only the element nodes, using properties nextElementSibling and previousElementSibling.

javascript
1let middle = document.getElementById("second");
2
3console.log(middle.previousElementSibling);
4console.log(middle.nextElementSibling);