When the browser loads a page, it reads the HTML and builds in memory a tree of objects: the DOM (Document Object Model). Each tag becomes an element object, with properties and methods. JavaScript can read and modify this tree; the browser then updates the display automatically.
<body>
<h1>Store</h1>
<ul class="produits">
<li class="produit" data-id="12">Sac <span class="prix">59 €</span></li>
<li class="produit" data-id="15">Gourde <span class="prix">12 €</span></li>
</ul>
</body>
Here, body has two children (h1 and ul), ul has two li children, and so on. The Elements tab of the inspector shows exactly this tree, in its current state (including changes made by JavaScript), whereas “View page source” shows the original HTML.
Loading the script at the right time
A script can only manipulate elements that have already been built. The modern solution:
<head>
<script src="app.js" defer></script> <!-- executed after the HTML is parsed -->
<!-- or -->
<script type="module" src="app.js"></script> <!-- modules are deferred by default -->
</head>
Without defer or type="module", a script placed in the <head> runs before the <body> exists: your selections will return null.
Selecting with CSS selectors
You already know CSS selectors: they are also used in JavaScript.
// first matching element, or null if there is none
const titre = document.querySelector("h1");
const liste = document.querySelector(".produits");
const sac = document.querySelector('[data-id="12"]');
// all matching elements: a NodeList
const produits = document.querySelectorAll(".produit");
console.log(produits.length); // 2
for (const p of produits) { // a NodeList is iterated with for...of
console.log(p.textContent);
}
produits.forEach(p => console.log(p.dataset.id)); // or forEach
// to use map/filter, convert to a real array
const ids = [...produits].map(p => p.dataset.id); // ["12", "15"]
The NodeList returned by querySelectorAll is static: it doesn’t update if elements are added afterwards. You have to select again.
document.getElementById("panier") is still common for an id (without the #). The older getElementsByClassName methods return “live” collections with more surprising behavior: prefer querySelectorAll.
Searching inside an element and navigating
const premier = document.querySelector(".produit");
premier.querySelector(".prix"); // searches only inside this li
premier.parentElement; // the <ul>
premier.children; // its child elements
premier.nextElementSibling; // the next li
premier.closest("ul"); // the nearest matching ancestor (or itself)
premier.matches(".produit"); // true: does the element match the selector?
closest will be very useful for events, in the lesson on delegation.
Planning for absence
const banniere = document.querySelector(".banniere-promo");
if (banniere) {
banniere.hidden = false;
}
// or with optional chaining for a read:
const texte = document.querySelector(".slogan")?.textContent ?? "";
Calling a method on null causes the classic error Cannot read properties of null. If you see it, check your selector (typo, forgotten dot before a class) and the script’s loading time.
Testing in the console
Open the browser console on any page and type document.querySelectorAll("a").length: you have just queried the DOM. It is the best playground for experimenting.
Exercises
Solutions and tests stay on the server. In the student space, your code is run and checked, and the tutor gives hints before the solution.
Build a CSS selector
Pure logic (no DOM): write
construireSelecteur({ tag, id, classes })that returns the corresponding CSS selector. All fields are optional; the order is tag, then#id, then the classes prefixed with a dot. Example:{ tag: "li", classes: ["produit", "promo"] }gives"li.produit.promo".function construireSelecteur({ tag = "", id, classes = [] } = {}) { // votre code }Select and count
In the script: select all the
.produitelements, then write the text"3 produits"into#compteur(the number must be computed). Then add the classpromo-visibleto each product that contains a.promoelement.<p id="compteur"></p> <ul> <li class="produit">Sac <span class="promo">-20 %</span></li> <li class="produit">Gourde</li> <li class="produit">Lampe <span class="promo">-10 %</span></li> </ul> <script> // votre code </script>Result of an empty selection
What does
document.querySelector(".inexistant")return?- An empty NodeList
- null
- undefined
End-of-lesson quiz
Why add defer to a
- To load the script later in the day
- To run it after the HTML is fully parsed
- To make it asynchronous with respect to the server
How do you apply map to a NodeList?
- liste.map(...)
- [...liste].map(...)
- liste.toArray().map(...)
What does element.closest(“.carte”) do?
- Returns the nearest .carte child
- Returns the element itself or its nearest matching ancestor
- Returns the next sibling
The rest of the course
- Modifying the DOM: Content, Classes, Attributes, and Creating Elements20 min
- Listening to Events20 min
- Event Propagation and Delegation15 min
- Forms, Interface State, and Local Storage20 min