In short
The DOM is the browser's live model of your web page. In this lesson you will learn what the DOM is and how to select elements with JavaScript using querySelector and friends. What is the DOM in JavaScript? When the browser loads an HTML file, it does not keep the text as it is.
The DOM is the browser's live model of your web page. In this lesson you will learn what the DOM is and how to select elements with JavaScript using querySelector and friends.
What is the DOM in JavaScript?
When the browser loads an HTML file, it does not keep the text as it is. It reads the HTML and builds a tree of objects in memory. This tree is called the DOM, short for Document Object Model. Every tag becomes an object called a node (or an element when it is a tag). JavaScript can read and change these objects, and the page on the screen updates right away.
Think of a family tree. At the top is one ancestor, and below are children, grandchildren and so on. The DOM works the same way. Take this small page:
<!DOCTYPE html>
<html lang="en">
<head>
<title>My Shop</title>
</head>
<body>
<h1>Welcome</h1>
<ul>
<li>Apples</li>
<li>Bread</li>
</ul>
</body>
</html>
The browser turns it into this tree:
document
└── html
├── head
│ └── title
│ └── "My Shop"
└── body
├── h1
│ └── "Welcome"
└── ul
├── li
│ └── "Apples"
└── li
└── "Bread"
The <body> is the parent of <h1> and <ul>. The two <li> elements are children of <ul>, and they are siblings of each other. The text inside a tag is its own small node, called a text node.
At the very top is the document object. It represents the whole page, and it is your starting point for everything in the DOM. Open the browser console on any page and type document.title to see the page title.
How to select elements with querySelector
Before you can change an element, you need to find it. The most useful method is document.querySelector(). You give it a CSS selector (the same selectors you use in CSS), and it returns the first matching element. If nothing matches, it returns null.
<!DOCTYPE html>
<html lang="en">
<head>
<title>Profile</title>
</head>
<body>
<h1 id="username">Maya Lopez</h1>
<p class="bio">Web developer from Lisbon.</p>
<p class="bio">Loves coffee and cats.</p>
<script>
const heading = document.querySelector("#username");
const firstBio = document.querySelector(".bio");
const missing = document.querySelector(".avatar");
console.log(heading.textContent);
console.log(firstBio.textContent);
console.log(missing);
</script>
</body>
</html>
Maya Lopez
Web developer from Lisbon.
null
What happens here:
"#username"selects by id, just like in CSS.".bio"selects by class, butquerySelectoronly returns the first.bioparagraph.".avatar"matches nothing, so you getnull. Trying to usemissing.textContentwould cause an error.
There is also document.getElementById("username"). You pass the id without the #. It is fast and you will see it in a lot of code, but querySelector can do everything it does and more, so many developers use querySelector for consistency.
Common mistake: Writing
document.getElementById("#username"). WithgetElementByIdyou must leave out the#, otherwise it returnsnull.
How to select many elements with querySelectorAll
To get all matching elements, use document.querySelectorAll(). It returns a NodeList, which is a list of elements. A NodeList looks like an array, but it is not quite one. You can use .length, for...of and .forEach(), but not methods like .map() or .filter(). If you need those, turn it into a real array with Array.from().
<!DOCTYPE html>
<html lang="en">
<head>
<title>Menu</title>
</head>
<body>
<ul class="menu">
<li class="dish">Pizza - $12</li>
<li class="dish">Pasta - $10</li>
<li class="dish">Salad - $8</li>
</ul>
<script>
const dishes = document.querySelectorAll(".menu .dish");
console.log(dishes.length);
for (const dish of dishes) {
console.log(dish.textContent);
}
const names = Array.from(dishes).map((dish) => dish.textContent.split(" - ")[0]);
console.log(names);
</script>
</body>
</html>
3
Pizza - $12
Pasta - $10
Salad - $8
["Pizza", "Pasta", "Salad"]
The selector ".menu .dish" finds every .dish inside .menu. The for...of loop visits each element one by one. Array.from(dishes) makes a real array, so you can use .map() to keep only the dish names.
You can also search inside one element instead of the whole page. For example, menu.querySelectorAll("li") only looks inside menu.
Note: If nothing matches,
querySelectorAllreturns an empty NodeList (length0), notnull. Your loop simply runs zero times.
Why your script needs defer
JavaScript can only select elements that already exist. The browser reads HTML from top to bottom. If your <script> is in the <head>, it runs before the <body> is built, so querySelector returns null.
You have two good fixes. One is to put the <script> at the end of the <body>, like the examples above. The modern way is to keep the script in the <head> and add the defer attribute:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Deferred script</title>
<script src="app.js" defer></script>
</head>
<body>
<h1>Hello</h1>
</body>
</html>
// app.js
const title = document.querySelector("h1");
console.log(title.textContent);
// Output: Hello
defer tells the browser: "Download this file now, but run it only after the whole HTML is read." The page loads fast and your code always finds the elements. defer only works with external files (a src attribute). Scripts with type="module" are deferred automatically.
Practice exercise
Create a page with a heading and a list of five favourite movies. Each <li> should have the class movie. In an external app.js file loaded with defer, select the heading and log its text. Then select all movies, log how many there are, and log each title with its position, like 1. Inception.
Hint: forEach gives you the index as the second argument: movies.forEach((movie, index) => { ... }). Remember that the index starts at 0.
Key takeaways
- The DOM is a tree of objects that the browser builds from your HTML, and
documentis its root. querySelectorreturns the first match ornull;querySelectorAllreturns a NodeList of all matches.- A NodeList works with
for...ofandforEach; useArray.from()to get array methods likemap. - Use
deferon scripts in the<head>so your code runs after the HTML is ready.
Frequently asked questions
Is the DOM the same as my HTML file?
No. The HTML file is just text. The DOM is the live object tree the browser builds from it. When JavaScript changes the DOM, the page changes, but your HTML file on disk stays the same.
Should I use getElementById or querySelector?
Both work. getElementById is a little faster, but the difference is too small to notice. querySelector accepts any CSS selector, so it is the more flexible choice for everyday code.
Why does querySelector return null?
Usually because the selector has a typo, or because the script ran before the element existed. Check the spelling and load your script with defer.