The easiest way to get acquainted with DOM trees is to study their structure. In short, DOM trees consist of nodes, which together form a hierarchy similar to HTML. Some nodes are leaf nodes, i.e., they don't contain other nodes (children) inside themselves, and some are internal ; they have children. Specific nodes most often describe specific tags from HTML and contain their attributes inside themselves. Nodes have a type that defines a set of node properties and methods. We'll get to know them below.
The root element in the DOM tree corresponds to the <html>
. tag. You can access it like this:
const html = document.documentElement;
// The node's tagName property contains the name of the tag in uppercase
console.log(html.tagName); // => 'HTML'
// HTML tag content in the form of DOM tree nodes.
// The text is also represented by a node
html.childNodes; // [head, text, body]
// Because head is higher than body
html.firstChild; // <head>...</head>
html.lastChild; // <body>...</body>
// Second child. Appeal by index.
html.childNodes[1]; // #text
Due to the fact that <body>
and <head>
are always present inside the document, they've been moved to the document object level for easier access:
document.head;
document.body;
You can go both up and down the tree:
// The parent of the body is html
document.documentElement === document.body.parentNode; // true
document.body === document.body.childNodes[2].parentNode; // true
Essentially, if you imagine a tree, then you can move along it both up to the parent, down to the child, and left and right to siblings. The picture below shows this:
childNodes
is a property that can be used to get children, i.e., nodes nested in the current one (but only one level of nesting). They also say that these are descendants of the first level. There are several interesting points to note while working with childNodes
.
This property is read-only. Trying to write something to a specific element won't work:
// There'll be no error, but nothing will change
document.body.childNodes[0] = 'hey';
A special set of methods are used to change DOM trees; we'll discuss them in the corresponding lesson.
Although childNodes
returns a set of elements, it's still not an array. It lacks the usual methods, such as map()
, filter()
and others. However, it does have forEach()
:
// NodeList type
const nodes = document.documentElement.childNodes;
nodes.forEach((el) => console.log(el));
If you really want to, then you can convert it into an array and then work with it in the usual way:
const list = Array.from(nodes);
// Now we have a regular array and there are methods available, for example filter
// We can filter the elements we need:
const filtered = list.filter((item) => item.textContent.includes('Navigating the DOM Tree'));
// and extract data from them, such as tag names
const filteredTags = filtered.map((item) => item.tagName);
console.log(filteredTags); // => 'HEAD', 'BODY']
The nodes in the DOM tree not only have types, they also build a hierarchy (the general-private relationship). In the hierarchy, subtypes inherit the properties and methods of their parent types, and add their own specific ones:
// The easiest way to view the type
document.body.toString(); // "[object HTMLBodyElement]"
document.body instanceof HTMLBodyElement; // true
Nodes with the Text and Comment types are leaf nodes, meaning they cannot have children. But Element types are what you have to deal with most often. Elements include all types represented by tags in HTML.
Whenever you work with a tree, you can expect to see children and descendants. In relation to the DOM tree, this means that the tag has a body, children, and descendants. How do they differ from each other?
const html = `
<html>
<head></head>
<body>
<div id="parent-div">
<h1>Header</h1>
Hello!
<div class="child-div">
<span>Some <b>text</b></span>
<ol>
<li>1</li>
<li>2</li>
</ol>
<!-- End List -->
</div>
</div>
</body>
</html>
`;
<div>
teg (with id parent-div
) contains three child nodes and 14 descendants. Why?
Child nodes: <h1>
, text "Hello!"
and <div>
(with thechild-div
class). Children (in relation to the node) are only those nodes that lie directly in it, i.e., are at the first level of nesting.
Descendants, in relation to a node, are all nodes nested in it at all levels of nesting. The descendants of the <div>
(with id parent-div
), in addition to the above three child tags, are also all nodes nested in these child tags (as well as nodes nested in these nested nodes and so on and so forth, given the recursive nature of trees).
Child nodes are also descendants. However, this isn't the same the other way round: the descendant is not necessarily a child element (in the example, the tag <span>
is a descendant, but not a child in relation to the <div>
with id parent-div
).
In practice, it's usually elements we're interested, not just any nodes. It's elements that we want to manipulate and move through. This is so important that the DOM has an alternative way of traversing the tree, which is built only on elements.
All these methods return Element type objects and skip Text and Comment objects. This can be seen in the example below, where the children
property returns only tags. This is how children
differs from childNodes
, which returns all nodes, including leaf nodes.
const node = document.documentElement;
node.children; // [head, body]
There is another rather important difference between children
and childNodes
. They return not only a different set of nodes, but also the type of collection itself is different in the first and second cases. childNodes
returns a NodeList, and children
returns an HTMLCollection. They work a little differently, but it'll be better to look at the difference a little later, when we get acquainted with selectors.
Some elements, such as forms and tables, have special properties for navigating through them.
<table>
<tr>
<td>1.1</td>
<td>1.2</td>
<td>1.3</td>
</tr>
<tr>
<td>2.1</td>
<td>2.2</td>
<td>2.3</td>
</tr>
</table>
const table = document.body.firstElementChild
table.rows[0].cells[2];
This method of navigation does not replace the main ones. It's made solely for convenience in places where it makes sense.
Do I need to know all these methods off by heart? In reality, no. It's important to understand the general principles of how DOM trees are structured, and to know the hierarchy of types and the basics of how to traverse through elements. You can always read about specific methods and properties in the documentation. Few people remember them by heart, and there are no practical reasons to do so.
In addition, traversing through trees in these ways is a low–level way of working. In practice, the main way to select the elements we need is selectors, we'll look at them later.
document.body
get to the deepest nodes containing this text.The Hexlet support team or other students will answer you.
A professional subscription will give you full access to all Hexlet courses, projects and lifetime access to the theory of lessons learned. You can cancel your subscription at any time.
Programming courses for beginners and experienced developers. Start training for free
Our graduates work in companies:
From a novice to a developer. Get a job or your money back!
Sign up or sign in
Ask questions if you want to discuss a theory or an exercise. Hexlet Support Team and experienced community members can help find answers and solve a problem.