Делегирование событий
Один обработчик на родительском элементе вместо отдельных обработчиков на каждом дочернем — использует всплытие событий.
Задача
Нужно обработать клики по элементам списка: кнопкам «удалить», «редактировать» и т.д. Элементы могут добавляться динамически, поэтому вешать обработчик на каждый из них неудобно.
Решение
<ul class="task-list" id="taskList">
<li class="task" data-id="1">
Задача 1
<button class="task__edit" data-action="edit">Ред.</button>
<button class="task__delete" data-action="delete">✕</button>
</li>
<li class="task" data-id="2">
Задача 2
<button class="task__edit" data-action="edit">Ред.</button>
<button class="task__delete" data-action="delete">✕</button>
</li>
</ul>
const list = document.getElementById('taskList');
// Один обработчик на весь список
list.addEventListener('click', (e) => {
const button = e.target.closest('[data-action]');
if (!button) return; // клик был не по кнопке
const action = button.dataset.action;
const taskEl = button.closest('.task');
const taskId = taskEl?.dataset.id;
if (action === 'delete') {
taskEl.remove();
console.log('Удалено:', taskId);
}
if (action === 'edit') {
console.log('Редактировать:', taskId);
}
});
// Добавление новых элементов — обработчик уже работает без изменений
function addTask(id, text) {
const li = document.createElement('li');
li.className = 'task';
li.dataset.id = id;
li.innerHTML = `
${text}
<button data-action="edit">Ред.</button>
<button data-action="delete">✕</button>
`;
list.appendChild(li);
}
Универсальная утилита делегирования:
function delegate(parent, selector, event, handler) {
parent.addEventListener(event, (e) => {
const target = e.target.closest(selector);
if (target && parent.contains(target)) {
handler.call(target, e);
}
});
}
// Использование
delegate(list, '[data-action="delete"]', 'click', function {
this.closest('.task').remove();
});
Ключевые моменты
e.target.closest(selector)— находит ближайшего предка (или себя), соответствующего селектору; безопасно при клике на вложенный элемент (иконку внутри кнопки).data-actionатрибут — удобный способ хранить тип действия без проверок по классу или тегу.- Обработчик на родителе работает и для динамически добавленных дочерних элементов.
parent.contains(target)— проверяем, что target находится внутри делегирующего контейнера.
Когда НЕ использовать
- Для событий, не всплывающих (
focus,blur,scroll) — используйcapture: trueили добавляй обработчик напрямую. - Если дерево очень глубокое и клик проходит через много уровней — может быть медленнее прямых обработчиков.