Javascript fondamentaux
Thème :
Intermédiaire
1 vues ·8/19 leçons

Event Delegation (Délégation d’événements) en JavaScript

Concept fondamental

La délégation d’événements est une technique qui consiste à ajouter un seul écouteur d’événement au parent au lieu d’en ajouter un à chaque enfant. L’écouteur parent “capture” les clics sur tous les enfants grâce au bubbling (remontée d’événement).


Pourquoi utiliser la délégation d’événements ?

❌ Approche SANS délégation (Mauvaise)

// Si vous avez 100 éléments <li>, vous créez 100 écouteurs !
const items = document.querySelectorAll('li');

items.forEach((item) => {
    item.addEventListener('click', () => {
        console.log('Cliqué :', item.target.textContent);
    });
});

Problèmes :

  • ⚠️ Crée 100 écouteurs différents
  • ⚠️ Consomme beaucoup de mémoire
  • ⚠️ Si vous ajoutez un nouvel <li> dynamiquement, il n’aura pas d’écouteur
  • ⚠️ Code lourd et inefficace

✅ Approche AVEC délégation (Bonne)

// Un seul écouteur sur le parent
const liste = document.querySelector('ul');

liste.addEventListener('click', (event) => {
    if (event.target.tagName === 'LI') {
        console.log('Cliqué :', event.target.textContent);
    }
});

Avantages :

  • ✅ Un seul écouteur, peu importe le nombre d’enfants
  • ✅ Économe en mémoire
  • ✅ Fonctionne aussi pour les éléments ajoutés dynamiquement
  • ✅ Code plus simple et performant

Comment fonctionne le Bubbling ?

Le bubbling (remontée d’événement) est le mécanisme qui permet à la délégation de fonctionner.

Schéma de propagation :

┌─────────────────────────────────┐
│         <ul> (parent)           │  ← L'écouteur est ici
│  ┌───────────────────────────┐  │
│  │  <li>Élément 1</li>       │  │ ← Vous cliquez ici
│  └───────────────────────────┘  │
│  ┌───────────────────────────┐  │
│  │  <li>Élément 2</li>       │  │
│  └───────────────────────────┘  │
└─────────────────────────────────┘

Flux de l'événement :
1. Clic sur <li> (target)
2. Remonte vers <ul> (parent)
3. L'écouteur du parent capture l'événement

Détail du code : Méthode 1 avec tagName

const liste = document.querySelector('ul');

liste.addEventListener('click', (event) => {
    // Vérifier si c'est un <li>
    if (event.target.tagName === 'LI') {
        console.log('Élément cliqué :', event.target.textContent);
    }
});

Explication ligne par ligne :

Code Explication
liste.addEventListener('click', ...) Ajoute un écouteur au parent <ul>
event.target L’élément exactement cliqué (peut être <li> ou un enfant du <li>)
event.target.tagName Le nom de la balise HTML (ex: ‘LI’, ‘SPAN’, ‘BUTTON’)
=== 'LI' Vérifie si c’est bien un élément <li>
event.target.textContent Récupère le texte de l’élément

Exemple HTML :

<ul id="liste">
    <li>Élément 1</li>
    <li>Élément 2</li>
    <li>Élément 3</li>
</ul>

Résultat :

Clic sur "Élément 1" → Affiche : "Élément cliqué : Élément 1"
Clic sur "Élément 2" → Affiche : "Élément cliqué : Élément 2"
Clic sur "Élément 3" → Affiche : "Élément cliqué : Élément 3"

Détail du code : Méthode 2 avec closest() (Recommandée)

liste.addEventListener('click', (event) => {
    // Chercher l'ancêtre <li> le plus proche
    const item = event.target.closest('li');
    if (item) {
        console.log('Élément :', item.textContent);
    }
});

Qu’est-ce que closest() ?

closest() remonte l’arborescence DOM jusqu’à trouver un élément correspondant au sélecteur.

Comparaison : tagName vs closest()

Problème avec tagName :

<ul>
    <li><strong>Texte en gras</strong> ← Vous cliquez ici</li>
</ul>
liste.addEventListener('click', (event) => {
    if (event.target.tagName === 'LI') {
        console.log('Cliqué sur LI');
    }
});
// ❌ Ne s'affiche PAS car event.target est <strong>, pas <li>

Solution avec closest() :

liste.addEventListener('click', (event) => {
    const item = event.target.closest('li');
    if (item) {
        console.log('Cliqué sur LI ou ses enfants');
    }
});
// ✅ S'affiche car closest() remonte jusqu'à <li>

Fonctionnement détaillé de closest()

const item = event.target.closest('li');

Étapes :

1. event.target = <strong>Texte en gras</strong>
2. closest('li') cherche : "Es-tu un <li> ?" → NON
3. Remonte au parent : <li>
4. closest('li') cherche : "Es-tu un <li> ?" → OUI ✅
5. Retourne : <li>Texte en gras</li>

Cas d’usage pratiques

1. Liste de tâches dynamique

<ul id="todos">
    <li>Tâche 1 <button class="delete">✕</button></li>
    <li>Tâche 2 <button class="delete">✕</button></li>
    <li>Tâche 3 <button class="delete">✕</button></li>
</ul>
const todoList = document.querySelector('#todos');

todoList.addEventListener('click', (event) => {
    // Clic sur le bouton supprimer
    if (event.target.classList.contains('delete')) {
        const item = event.target.closest('li');
        item.remove();
        console.log('Tâche supprimée');
    }

    // Clic sur l'élément lui-même
    if (event.target.tagName === 'LI') {
        event.target.classList.toggle('completed');
    }
});

// Ajouter une nouvelle tâche dynamiquement
const newItem = document.createElement('li');
newItem.textContent = 'Tâche 4';
newItem.innerHTML += ' <button class="delete">✕</button>';
todoList.appendChild(newItem);
// ✅ Fonctionne automatiquement sans ajouter d'écouteur !

2. Tableau avec actions

<table id="users">
    <tr>
        <td>Alice</td>
        <td><button class="edit">Éditer</button></td>
    </tr>
    <tr>
        <td>Bob</td>
        <td><button class="edit">Éditer</button></td>
    </tr>
</table>
const table = document.querySelector('#users');

table.addEventListener('click', (event) => {
    if (event.target.classList.contains('edit')) {
        const row = event.target.closest('tr');
        const name = row.querySelector('td').textContent;
        console.log('Éditer :', name);
    }
});

3. Menu avec sous-éléments

<nav id="menu">
    <ul>
        <li class="item">
            Accueil
            <ul>
                <li class="subitem">Sous-menu 1</li>
            </ul>
        </li>
    </ul>
</nav>
const menu = document.querySelector('#menu');

menu.addEventListener('click', (event) => {
    // Clic sur un élément de menu
    const item = event.target.closest('.item');
    if (item) {
        console.log('Menu cliqué :', item.textContent);
    }

    // Clic sur un sous-élément
    const subitem = event.target.closest('.subitem');
    if (subitem) {
        console.log('Sous-menu cliqué :', subitem.textContent);
    }
});

Comparaison : tagName vs closest() vs matches()

Méthode Utilisation Avantage
tagName === 'LI' Vérifier la balise exacte Simple, rapide
closest('li') Chercher l’ancêtre Fonctionne avec les enfants imbriqués
matches('li') Vérifier le sélecteur actuel Flexible avec les classes/IDs
// Exemple avec matches()
liste.addEventListener('click', (event) => {
    if (event.target.matches('li.active')) {
        console.log('Élément actif cliqué');
    }
});

Avantages résumés

Aspect Sans délégation Avec délégation
Nombre d’écouteurs 100 (pour 100 éléments) 1
Mémoire Élevée Basse
Éléments dynamiques ❌ Pas de listener ✅ Fonctionne
Performance Lente Rapide
Maintenabilité Difficile Facile

Pièges à éviter

❌ Mauvais : Oublier de vérifier la cible

liste.addEventListener('click', (event) => {
    // Cela s'exécute pour TOUS les clics, même sur <ul>
    console.log('Cliqué !');
});

✅ Bon : Toujours vérifier

liste.addEventListener('click', (event) => {
    const item = event.target.closest('li');
    if (item) {
        console.log('Cliqué sur un <li>');
    }
});

Résumé

Concept Explication
Bubbling L’événement remonte du enfant vers le parent
Délégation Ajouter un écouteur au parent au lieu de chaque enfant
event.target L’élément exactement cliqué
closest() Chercher l’ancêtre correspondant au sélecteur
tagName Vérifier le type de balise HTML

La délégation d’événements est essentielle pour du code performant et maintenable ! 🚀

Prêt à passer à la suite ?

Vous avez terminé cette leçon sur Event Delegation (Délégation d'événements) en JavaScript. Continuez votre apprentissage avec la prochaine leçon ou explorez d'autres formations.