frontend / javascript
DOM : sélection et manipulation
Explication
Ce que vous allez apprendre
- Sélectionner un ou plusieurs éléments avec
querySelectoretquerySelectorAll - Distinguer une
NodeListstatique d'uneHTMLCollection"live" - Choisir entre
textContentetinnerHTMLselon l'origine du contenu à afficher - Expliquer pourquoi insérer du HTML venant d'un utilisateur avec
innerHTMLest dangereux - Regrouper des modifications du DOM avec un
DocumentFragmentpour limiter les reflows
Dans quel contexte ?
Un développeur ajoute une fonctionnalité de commentaires sur un article de blog. Son code affiche le texte saisi par le visiteur avec zoneCommentaire.innerHTML = commentaireUtilisateur. Ça fonctionne en démo, jusqu'à ce qu'un testeur tape <img src=x onerror="alert('piraté')"> dans le champ commentaire : le script s'exécute dans le navigateur de tous les visiteurs qui lisent ce commentaire. Remplacer innerHTML par textContent aurait suffi à neutraliser l'attaque, puisque le texte n'aurait alors jamais été interprété comme du HTML.
1. Le pont entre le code et la page affichée
Jusqu'ici, tout le cours portait sur le langage JavaScript "pur", indépendant de tout environnement. Cette leçon change de terrain : elle aborde le DOM (Document Object Model), la représentation en mémoire de la page HTML que le navigateur construit, et que JavaScript peut lire et modifier en temps réel.
2. D'abord, sélectionner un élément
Sélectionner un élément est la première étape de toute interaction. querySelector et querySelectorAll, basés sur la syntaxe des sélecteurs CSS, ont largement remplacé les anciennes méthodes comme getElementsByClassName.
3. Une nuance utile entre les deux résultats
Ces méthodes retournent des collections légèrement différentes : une NodeList (via querySelectorAll) supporte forEach directement, alors qu'une HTMLCollection "live" (méthodes historiques) se met à jour automatiquement si le DOM change — un comportement parfois surprenant si on ne s'y attend pas.
| Méthode | Type retourné | Se met à jour si le DOM change ? |
|---|---|---|
querySelector | Élément unique (ou null) | Non applicable |
querySelectorAll | NodeList statique | Non |
getElementsByClassName | HTMLCollection live | Oui |
getElementsByTagName | HTMLCollection live | Oui |
4. Le point de vigilance le plus important
Une fois l'élément sélectionné, il faut souvent modifier son contenu. Ici, une distinction est essentielle : textContent traite toujours son contenu comme du texte brut, jamais interprété — c'est la méthode sûre. innerHTML, lui, interprète le contenu comme du HTML, ce qui devient dangereux dès que ce contenu provient d'un utilisateur (une leçon entière, plus loin dans le cours, y sera consacrée).
Piège fréquent
Ne jamais faire element.innerHTML = donneeVenantDUnUtilisateur. Si le contenu doit rester du texte, utilisez textContent. Si vous devez réellement insérer du HTML de confiance (jamais fourni par un utilisateur non filtré), envisagez textContent combiné à la création d'éléments via createElement, ou une bibliothèque de sanitization dédiée.
5. Un dernier réflexe de performance
Modifier le DOM a un coût : chaque insertion peut déclencher un recalcul de la mise en page ("reflow"). Regrouper les modifications via un DocumentFragment, ou via une seule affectation plutôt que des centaines d'insertions unitaires, évite de payer ce coût à chaque étape.
Astuce
Pour insérer 100 éléments dans une liste, construisez-les tous dans un DocumentFragment en mémoire, puis faites un seul liste.appendChild(fragment) : un seul reflow au lieu de cent.
Commandes & code
DOM : sélection et manipulation
Interagir avec la page HTML depuis JavaScript.
// --- Selection d'elements ---
const parId = document.getElementById("mon-id");
const parSelecteur = document.querySelector(".ma-classe"); // premier match
const tousLesMatch = document.querySelectorAll("ul.liste li"); // NodeList (tous les matchs)
const parClasse = document.getElementsByClassName("item"); // HTMLCollection LIVE
const parTag = document.getElementsByTagName("li");
// NodeList vs HTMLCollection : NodeList est "statique" (querySelectorAll), HTMLCollection est LIVE
// (elle se met a jour automatiquement si le DOM change)
tousLesMatch.forEach((el) => console.log(el.textContent)); // NodeList supporte forEach
// parClasse.forEach(...) // ERREUR : HTMLCollection n'a pas forEach nativement
// --- Creation et insertion d'elements ---
const nouvelElement = document.createElement("div");
nouvelElement.textContent = "Contenu genere";
nouvelElement.classList.add("carte", "carte--active");
nouvelElement.setAttribute("data-id", "123");
nouvelElement.dataset.role = "widget"; // acces aux data-* via .dataset
const conteneur = document.querySelector("#conteneur");
conteneur.appendChild(nouvelElement);
conteneur.append(nouvelElement, "texte brut"); // append accepte plusieurs args et du texte
conteneur.prepend(nouvelElement); // insere en premier
conteneur.insertBefore(nouvelElement, conteneur.firstChild);
// insertAdjacentHTML : insertion performante sans parser/re-render tout le sous-arbre
conteneur.insertAdjacentHTML("beforeend", "<p>Ajoute directement</p>");
// --- Modification du contenu ---
const element = document.querySelector("#titre");
element.textContent = "Nouveau texte"; // echappe le HTML : SAFE contre le XSS
element.innerHTML = "<strong>Gras</strong>"; // interprete le HTML : DANGEREUX avec du contenu non fiable
// --- Classes et styles ---
element.classList.add("actif");
element.classList.remove("inactif");
element.classList.toggle("visible"); // bascule
element.classList.contains("actif"); // teste la presence
element.style.color = "red"; // style inline
element.style.setProperty("--couleur-theme", "blue"); // variable CSS custom
// --- Attributs ---
element.getAttribute("href");
element.setAttribute("aria-label", "Fermer");
element.removeAttribute("disabled");
element.hasAttribute("required");
// --- Navigation dans le DOM ---
console.log(element.parentElement);
console.log(element.children); // enfants elements (pas les text nodes)
console.log(element.childNodes); // TOUS les noeuds (incluant texte/commentaires)
console.log(element.nextElementSibling);
console.log(element.closest(".carte")); // remonte jusqu'au premier ancetre matchant
// --- Suppression ---
element.remove(); // API moderne, directe
// element.parentNode.removeChild(element); // API historique, plus verbeuse
// --- DocumentFragment : batching des insertions pour la performance ---
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
const li = document.createElement("li");
li.textContent = `Item ${i}`;
fragment.appendChild(li); // aucun reflow tant qu'on reste dans le fragment
}
document.querySelector("#liste").appendChild(fragment); // UN SEUL reflow a la fin
// --- Mauvaise pratique vs bonne pratique pour des insertions en boucle ---
// MAUVAIS : provoque un reflow/repaint a CHAQUE iteration
// for (let i = 0; i < 1000; i++) {
// document.querySelector("#liste").innerHTML += `<li>${i}</li>`;
// }
// BON : construire une chaine puis une seule affectation, ou utiliser un DocumentFragment
const html = Array.from({ length: 1000 }, (_, i) => `<li>${i}</li>`).join("");
document.querySelector("#liste").innerHTML = html; // un seul reflowRésumé
textContentéchappe le contenu (sûr),innerHTMLl'interprète (risque XSS avec des données non fiables).HTMLCollection(getElementsByClassName) est "live" ;NodeList(querySelectorAll) est statique.- Un
DocumentFragmentregroupe des insertions multiples en un seul reflow, gain de performance notable. closest()remonte l'arbre jusqu'au premier ancêtre matchant un sélecteur.
Exercices pratiques
Mission : le commentaire piégé du blog
Objectif : Corriger une faille d'injection HTML sur une zone de commentaires, et optimiser une insertion de 500 éléments qui rame.
Contexte
Sur le blog de Technologik, un testeur a saisi <img src=x onerror="alert('pirate')"> dans le champ commentaire, et le message d'alerte s'est affiché chez tous les visiteurs de l'article. Le code fautif :
const zoneCommentaire = document.querySelector("#commentaires");
zoneCommentaire.innerHTML += `<p>${commentaireUtilisateur}</p>`;Ce même bloc de code, appelé une fois par commentaire affiché (jusqu'à 500 sur les articles populaires), rend aussi la page très lente au chargement. Tu dois corriger la faille de sécurité et le problème de performance.