Кратко
Делегирование — паттерн: вместо того, чтобы вешать listener на каждый элемент, вешаем ОДИН listener на родителя. При событии проверяем event.target, чтобы понять, какой дочерний элемент был задействован. Это работает благодаря всплытию (bubbling) и автоматически обслуживает динамически добавленные элементы.
Подробно
Проблема без делегирования:
const items = document.querySelectorAll('.item'); items.forEach(item => { item.addEventListener('click', handleClick); // 100 listeners! });
Проблемы:
- Много listener'ов = память
- Динамически добавленные элементы не будут работать
- Нужно снимать listener'и при удалении элементов
Решение — делегирование:
const list = document.querySelector('.list');
list.addEventListener('click', (e) => { const item = e.target.closest('.item'); if (item) { console.log('Клик по:', item.textContent); } });
Почему это работает:
- Клик на .item → событие всплывает до .list
- На .list ОДИН listener
- e.target.closest('.item') находит ближайший элемент с классом
- Автоматически работает для новых элементов!
data-* атрибуты для идентификации:
<ul class="tabs"> <li data-tab="home">Главная</li> <li data-tab="about">О нас</li> <li data-tab="contacts">Контакты</li> </ul>
document.querySelector('.tabs').addEventListener('click', (e) => { const tab = e.target.closest('[data-tab]'); if (tab) { activateTab(tab.dataset.tab); } });
Когда делегирование НЕ нужно:
- Мало статических элементов (5-10) — прямые listener'и проще
- Событие не всплывает (focus, blur)
- Нужна сложная логика, зависящая от конкретного элемента
Ментальная модель
Делегирование — как ресепшен в офисе. Вместо того чтобы навесить звонок на каждую дверь (100 listener'ов), один звонок на ресепшн. Когда кто-то стучит — ресепшен проверяет, в какую дверь постучали (event.target), и направляет звонок нужному сотруднику.
Примеры
**Самый простой:**
<ul id="list">
<li>Яблоко</li>
<li>Банан</li>
<li>Вишня</li>
</ul>
<script>
const list = document.getElementById('list');
list.addEventListener('click', (e) => {
if (e.target.tagName === 'LI') {
console.log('Выбрано:', e.target.textContent);
}
});
</script>Делегирование: один listener на ul, проверяем tagName.
**Простой:**
<div class="todo-list">
<button data-action="add">Добавить</button>
<ul>
<li data-id="1">
Задача 1
<button data-action="delete">×</button>
</li>
</ul>
</div>
<script>
const todoList = document.querySelector('.todo-list');
todoList.addEventListener('click', (e) => {
const action = e.target.dataset.action;
const id = e.target.closest('[data-id]')?.dataset.id;
if (action === 'delete' && id) {
console.log(`Удалить задачу ${id}`);
}
});
</script>Делегирование с data-* атрибутами: определяем действие и ID через dataset.
**Реальный пример:**
// Табы с делегированием
document.querySelector('.tabs').addEventListener('click', (e) => {
const tab = e.target.closest('[data-tab]');
if (!tab) return;
// Убираем active у всех
document.querySelectorAll('.tabs [data-tab]').forEach(t => {
t.classList.remove('active');
});
// Добавляем active выбранному
tab.classList.add('active');
// Показываем нужный контент
const targetId = tab.dataset.tab;
document.querySelectorAll('.tab-content').forEach(content => {
content.style.display = content.id === targetId ? 'block' : 'none';
});
});Реальные табы: делегирование + closest + data-* для переключения контента.
Частые ошибки
**Неправильно:** Использовать e.target без closest
**Почему:** e.target — тот элемент, куда кликнули. Если внутри li есть span — target будет span, а не li.
**Правильно:** Используйте e.target.closest('.selector'), чтобы найти нужный родительский элемент.
**Неправильно:** Считать, что делегирование ВСЕГДА быстрее
**Почему:** Для 5-10 статических элементов прямые listener'и проще. Делегирование — архитектурный паттерн для динамического контента.
**Правильно:** Делегирование полезно для: динамических списков, табов, модалок. Не для каждого случая.
**Неправильно:** Забывать проверять, что closest вернул элемент
**Почему:** Клик по пустому месту в контейнере: closest вернёт null.
**Правильно:** Всегда проверяйте: if (!element) return;
Важно запомнить
- Делегирование = один listener на родителе
- Работает благодаря всплытию (bubbling)
- e.target.closest('.selector') для поиска элемента
- Автоматически работает для новых элементов
- data-* атрибуты для передачи данных
- Не всегда нужен — оцените пользу
Связь
Назад: Вы понимаете всплытие событий (DA2) — теперь вы используете его для эффективных обработчиков.
Вперёд: Следующий урок (DA4) — как отменять действия браузера по умолчанию: переход по ссылке и отправку формы.