Apprenez en lisant dans l'ordre

L'arbre DOM et la sélection d'éléments — querySelector et querySelectorAll

querySelector et querySelectorAll pour récupérer des éléments dans l'arbre DOM : recherche par classe, id et attribut data, conversion d'une NodeList en tableau, et ?. quand l'élément manque.

Tu veux lire le prix de tous les produits d'une liste. Chercher par id, comme #total, ne marche que pour les éléments qui ont un id : ce n'est pas adapté à une liste de produits qui ont tous la même structure.

Cet article couvre les sélecteurs, qui trouvent des éléments selon d'autres critères que l'id, querySelectorAll, qui rassemble toutes les correspondances, et la façon de lire un élément qui risque d'être absent.

Récupérer un seul élément — querySelector et les sélecteurs

Dans une liste d'annonces, tu ne veux lire que le titre de l'annonce importante. La liste est faite de li identiques, dont aucun n'a d'id. Un id est un nom qu'un seul élément de la page peut porter : il ne peut donc pas servir à marquer une catégorie, comme « les annonces importantes ».

Un sélecteur (une chaîne qui décrit l'élément recherché) désigne des éléments par nom de balise, comme li, par id, comme #news, ou par classe (un nom que tu donnes à tous les éléments du même type), comme .notice. document.querySelector(sélecteur) renvoie le premier élément de l'arbre DOM qui correspond.

// Préparer une liste d'annonces (un élément peut avoir plusieurs classes, séparées par des espaces)
document.body.innerHTML = `<ul id="news">
  <li class="notice">Frais de port modifiés</li>
  <li class="notice important">Maintenance imprévue</li>
  <li class="notice">Nouveautés d'automne</li>
</ul>`;

// Chercher par nom de balise / par classe (le nom de la classe suit le .)
console.log(document.querySelector("li").textContent);           // Frais de port modifiés
console.log(document.querySelector(".notice").textContent);      // Frais de port modifiés
console.log(document.querySelector(".important").textContent);   // Maintenance imprévue
L'élément trouvé par chaque sélecteur
élément body — parcouru de haut en bas
élément ul — id vaut news
1er li — classe notice
  • "li" et ".notice" — s'arrêtent tous les deux ici
  • Texte : « Frais de port modifiés »
2e li — classes notice et important
  • ".important" — s'arrête ici
  • Texte : « Maintenance imprévue »
3e li — classe notice
  • Texte : « Nouveautés d'automne »
  • Aucun des trois sélecteurs ne va jusqu'ici
Trois li ont la classe notice, mais ".notice" arrête de chercher au premier. Tu récupères le premier élément qui correspond.

Le deuxième li a deux classes : il correspond à la fois à .notice et à .important. Pour ne récupérer que lui, écris un critère auquel seul ce deuxième li correspond, comme .important.

Sur la page d'une liste de produits, récupère des éléments un par un en choisissant entre nom de balise, classe et id. Le HTML est déjà en place grâce à l'affectation à document.body.innerHTML en haut du code.

① Trouve le premier produit par son nom de balise et affiche son texte.

② Trouve le produit par la classe que seul le produit recommandé possède, et affiche son texte.

③ Trouve par son id l'élément qui indique le nombre de produits, et affiche son texte.

(Si tout s'exécute correctement, une explication apparaîtra.)

Éditeur JavaScript / TypeScript

Exécuter le code pour voir le résultat

Parcourir toute une liste — querySelectorAll et NodeList

Et si tu veux les titres de toutes les annonces d'un coup ? querySelector arrête de chercher à la première correspondance : tu as beau l'appeler plusieurs fois avec le même sélecteur, tu obtiens toujours « Frais de port modifiés », jamais la deuxième annonce ni les suivantes.

document.querySelectorAll(sélecteur) renvoie tous les éléments qui correspondent dans une NodeList (un objet semblable à un tableau qui contient les éléments trouvés, dans l'ordre de l'arbre DOM). length donne leur nombre, et forEach permet de les traiter un par un.

document.body.innerHTML = `<ul id="news">
  <li class="notice">Frais de port modifiés</li>
  <li class="notice important">Maintenance imprévue</li>
  <li class="notice">Nouveautés d'automne</li>
</ul>`;

// Récupérer tous les éléments qui correspondent dans une NodeList
const notices = document.querySelectorAll(".notice");
console.log(notices.length);   // 3
notices.forEach((notice) => console.log(notice.textContent));
// Frais de port modifiés
// Maintenance imprévue
// Nouveautés d'automne

// notices.map(...) lève une TypeError : convertir d'abord en tableau
const titles = [...notices].map((notice) => notice.textContent);
console.log(titles.join(" / "));   // Frais de port modifiés / Maintenance imprévue / Nouveautés d'automne
Convertir en tableau avant d'appeler map
notices.map(...)Une NodeListn'a pas de mapLève uneTypeErrortitles n'estjamais créé[...notices].map(...)Un tableau des3 mêmes élémentsLe map du tableautraite chacun["Frais de portmodifiés", …]3 chaînes
Appeler map directement sur la NodeList lève une erreur ; après conversion en tableau, ça marche. Appelle map et filter une fois la NodeList convertie en tableau.

Même sous forme de NodeList, tu peux lire un élément par son index, comme notices[1]. Le tableau créé avec la syntaxe de décomposition est un objet distinct : après la conversion, notices reste une NodeList, et tu peux continuer à l'utiliser avec forEach.

Sur la page d'un récapitulatif d'achat, lis tous les articles achetés d'un coup, puis vérifie combien il y en a et combien coûtent 2000 yens ou plus. Le HTML du récapitulatif est déjà en place grâce à l'affectation à document.body.innerHTML en haut du code.

① Récupère tous les éléments des articles du récapitulatif et affiche leur nombre.

② Parcours un par un les éléments récupérés et affiche le texte de chaque article dans l'ordre.

③ Récupère tous les éléments de prix, convertis-les en tableau, ne garde que ceux de 2000 yens ou plus, et affiche leur nombre.

Éditeur JavaScript / TypeScript

Exécuter le code pour voir le résultat

Désigner un élément par son numéro — attributs data et sélecteurs d'attribut

Cette fois, il te faut uniquement l'annonce numéro N-103. Si les mêmes annonces apparaissent aussi ailleurs sur la page, les id seront en double : l'id ne convient donc pas. Quant à la classe, elle sert à regrouper des éléments d'un même type, et ce n'est pas le bon endroit pour ranger un numéro.

Un attribut data (un attribut dont le nom commence par data-) peut contenir n'importe quelle valeur personnalisée, comme un numéro. Les sélecteurs qui cherchent par attribut s'écrivent entre crochets : [data-id] correspond aux éléments qui ont l'attribut, et [data-id="N-103"] à ceux dont la valeur correspond aussi.

document.body.innerHTML = `<ul id="news">
  <li class="notice" data-id="N-101">Frais de port modifiés</li>
  <li class="notice important" data-id="N-102">Maintenance imprévue</li>
  <li class="notice" data-id="N-103">Nouveautés d'automne</li>
</ul>`;

// Chercher uniquement selon la présence de l'attribut (les 3 correspondent)
console.log(document.querySelector("[data-id]").textContent);          // Frais de port modifiés

// Chercher un élément dont la valeur correspond aussi (la valeur est entre ", donc le sélecteur entier va entre ')
console.log(document.querySelector('[data-id="N-103"]').textContent);  // Nouveautés d'automne
D'un attribut à une chaîne de sélecteur
data-id="N-103"[data-id="N-103"]'[data-id="N-103"]'Attribut du linom="valeur"Les crochets enfont un sélecteurContient ", doncentouré de '
Entourer un attribut HTML de crochets en fait un sélecteur, et on l'entoure de ' pour le passer à JavaScript. Les " autour de la valeur et les ' extérieurs n'ont pas le même rôle.

Sans valeur, [data-id] correspond aux trois et renvoie le premier ; avec la valeur, [data-id="N-103"] ne désigne que celui dont le numéro correspond. Le tableau ci-dessous reprend les sélecteurs utilisés dans cet article et l'élément que chacun renvoie dans la liste d'annonces.

SélecteurCorrespond àRenvoyé dans les annonces
liBalise li1er li (Frais de port…)
.importantclass contient important2e li (Maintenance…)
#newsid vaut newsL'élément ul
[data-id]A un attribut data-id1er li (Frais de port…)
[data-id="N-103"]data-id vaut N-1033e li (Nouveautés…)

Sans guillemets, une valeur qui commence par un chiffre échoue

Certaines valeurs marchent sans guillemets, comme [data-id=N-103], mais une valeur qui commence par un chiffre, comme [data-id=102], ne peut pas être lue comme un sélecteur : la ligne lève alors une erreur « is not a valid selector ». Entoure toujours les valeurs de ".

Dans la liste de stock d'une papeterie, récupère les articles voulus à partir de leur référence et de leur marqueur de promotion. Le HTML de la liste de stock est déjà en place grâce à l'affectation à document.body.innerHTML en haut du code.

① Trouve l'élément dont la référence est P-203 et affiche son texte.

② Trouve l'élément qui a l'attribut de promotion et affiche son texte.

③ Récupère tous les éléments qui ont l'attribut de référence et affiche leur nombre.

Éditeur JavaScript / TypeScript

Exécuter le code pour voir le résultat

Continuer même si l'élément manque — null et ?.

Prends une page d'accueil qui n'affiche un titre que s'il y a une annonce urgente. Les jours où il n'y en a pas, l'élément n'existe pas : la ligne qui lit le texte du titre lève une TypeError, et rien de ce qui suit ne s'exécute.

Quand aucun élément ne correspond, querySelector renvoie null. Si tu ajoutes le chaînage optionnel ?. au résultat avant de lire .textContent, il renvoie undefined sans aller plus loin quand la valeur est null, et un ?? placé ensuite permet de fixer une valeur par défaut.

// Une liste sans annonce urgente (classe urgent) pour l'instant
document.body.innerHTML = `<ul><li class="notice">Nouveautés d'automne</li></ul>`;
const urgent = document.querySelector(".urgent");
console.log(urgent);                            // null
// urgent.textContent lèverait une TypeError
console.log(urgent?.textContent);               // undefined
console.log(urgent?.textContent ?? "Aucune");   // Aucune

// Dans une liste qui a une annonce urgente, comparer avec et sans le .
document.body.innerHTML = `<ul><li class="urgent">Panne du serveur</li></ul>`;
console.log(document.querySelector(".urgent")?.textContent ?? "Aucune");   // Panne du serveur
console.log(document.querySelector("urgent")?.textContent ?? "Aucune");    // Aucune
Trois recherches et le texte affiché
querySelector(…)?.textContent?? "Aucune"Liste sans urgent,cherche ".urgent"Liste avec urgent,cherche ".urgent"Liste avec urgent,cherche "urgent"null renvoyé,?. s'arrête làli trouvé,son texte est luCherche la baliseurgent : nullAffiche« Aucune »Affiche « Pannedu serveur »Il existe, maisaffiche « Aucune »
Les colonnes 1 et 3 passent toutes les deux de null à « Aucune ». ?. ne distingue pas un élément absent d'une faute de frappe dans le sélecteur.

Réserve ?. aux éléments qui peuvent manquer certains jours, comme l'annonce urgente. Si tu l'ajoutes aussi aux éléments toujours présents, tu ne verras plus tes fautes de frappe : n'en mets pas, laisse le code lever une TypeError, et sers-toi du numéro de ligne pour trouver la cause.

Une NodeList vide n'est pas null

Même quand rien ne correspond, querySelectorAll ne renvoie pas null, mais une NodeList dont length vaut 0. Appeler forEach dessus ne lève pas d'erreur et ne fait tout simplement rien : pour savoir qu'aucun élément n'a été trouvé, vérifie length === 0 plutôt que d'utiliser ?..

Sur la fiche d'un produit, lis les avis et le prix promotionnel, qui peuvent l'un comme l'autre être absents. Le HTML de la page produit est déjà en place grâce à l'affectation à document.body.innerHTML en haut du code.

① Affiche le texte du nom du produit.

② Récupère tous les li d'avis et, s'il n'y en a aucun, affiche « Aucun avis pour l'instant ».

③ Affiche le texte du prix promotionnel sans lever d'erreur si l'élément est absent.

④ S'il n'y a pas de prix promotionnel, affiche à la place le texte du prix normal.

Éditeur JavaScript / TypeScript

Exécuter le code pour voir le résultat
QUIZ

Vérification des connaissances

Répondez à chaque question une par une.

Question 1Avec 3 li qui ont data-id, que renvoie querySelector("[data-id]") ?

Question 2Que se passe-t-il si tu appelles map directement sur la valeur renvoyée par querySelectorAll(".notice") ?

Question 3Quand l'élément n'existe pas, quelle est la valeur de querySelector(".sale-price")?.textContent ?