Делегирование событий в JavaScript
Делегирование событий - это подход, при котором обработчик назначают не каждому однотипному элементу, а их общему родителю. Когда пользователь кликает по дочернему элементу, событие всплывает к родителю, и один обработчик решает, что нужно сделать.
Такой подход особенно полезен для списков, таблиц, карточек товаров и других повторяющихся элементов. Он уменьшает число обработчиков и сразу работает с элементами, которые появятся позже.
- Как работает делегирование
- Пример со списком задач
- Зачем нужен closest()
- Динамические элементы
- Когда делегирование не подходит
- Итого
Как работает делегирование
Без делегирования нужно найти все кнопки и назначить обработчик каждой из них. При делегировании обработчик находится на ближайшем стабильном родителе, например на ul со списком задач.
Внутри обработчика event.currentTarget будет этим родителем, а event.target - конкретным элементом, по которому кликнули. Поэтому сначала нужно найти нужную кнопку или строку списка, а уже потом выполнять действие.
Пример со списком задач
<ul class="task-list">
<li class="task-item">
Изучить события
<button class="remove-task" type="button">
<span aria-hidden="true">×</span>
Удалить
</button>
</li>
<li class="task-item">
Сделать практику
<button class="remove-task" type="button">
<span aria-hidden="true">×</span>
Удалить
</button>
</li>
</ul>
const taskList = document.querySelector('.task-list');
taskList.addEventListener('click', function (event) {
const removeButton = event.target.closest('.remove-task');
if (!removeButton || !taskList.contains(removeButton)) {
return;
}
const task = removeButton.closest('.task-item');
if (!task) {
return;
}
task.remove();
});
Обработчик назначен только списку taskList. Клик по любой кнопке Удалить всплывёт к списку, после чего код найдёт нужную задачу и удалит её. Обработчики на отдельных кнопках не нужны.
Зачем нужен closest()
Пользователь может кликнуть не по самой кнопке, а по иконке span внутри неё. В таком случае event.target будет указывать на span, и проверка класса прямо у event.target не найдёт кнопку.
Метод closest() начинает поиск с текущего элемента и поднимается к родителям, пока не найдёт элемент по CSS-селектору. В примере event.target.closest(‘.remove-task’) вернёт кнопку и при клике по её тексту, и при клике по иконке. Если подходящего элемента нет, метод вернёт null.
Проверка taskList.contains(removeButton) ограничивает поиск текущим списком. Она защищает код, если похожие кнопки есть в других частях страницы или разметка со временем изменится.
Не выполняйте действие для каждого клика на родителе. Сначала убедитесь, что пользователь нажал именно на нужный дочерний элемент. Для вложенной разметки обычно надёжнееclosest(), чем прямая проверкаevent.target.
Динамические элементы
Если позже добавить в taskList новую задачу с такой же кнопкой, обработчик уже будет работать. Он назначен родителю и не зависит от того, сколько дочерних элементов было на странице в момент загрузки.
Это удобно и при удалении задач: у удалённой кнопки нет собственного обработчика, который нужно отдельно снимать. Один обработчик остаётся на списке, пока существует сам список.
Когда делегирование не подходит
Делегирование лучше всего подходит для событий, которые всплывают. Например, click всплывает, а focus, blur, mouseenter и mouseleave - нет. Обычный обработчик на родителе такие события от потомков не получит.
Не всегда нужен и общий обработчик. Для одной отдельной кнопки прямой addEventListener() обычно понятнее. Кроме того, если дочерний компонент вызывает stopPropagation(), событие не дойдёт до делегирующего родителя.
Итого
Делегирование позволяет обрабатывать действия множества похожих элементов одним обработчиком на общем родителе. Для этого используйте всплывающее событие, находите нужный элемент через event.target.closest() и ограничивайте поиск нужным контейнером. Такой подход особенно полезен для списков и динамически добавляемых элементов.