Делегирование событий в JavaScript: понятное руководство с примерами
Делегирование событий в JavaScript — один из самых полезных паттернов работы с DOM. Вместо того чтобы назначать обработчик каждому элементу, мы ставим один обработчик на общий контейнер и используем всплытие событий. Это экономит память, упрощает код и отлично работает с динамически добавляемыми узлами.
Что такое делегирование событий
События в браузере всплывают от конкретного узла (event.target) вверх по дереву до документа. Делегирование использует это поведение: обработчик вешается на предка и решает, для кого из потомков событие предназначалось, фильтруя по селектору или атрибуту.
Базовый пример: меню действий в списке
<ul id="users">
<li>Алиса <button data-action="edit">Редактировать</button> <button data-action="delete">Удалить</button></li>
<li>Боб <button data-action="edit">Редактировать</button> <button data-action="delete">Удалить</button></li>
</ul>
const list = document.getElementById('users');
list.addEventListener('click', (e) => {
// Ищем ближайшую кнопку в цепочке всплытия
const btn = e.target.closest('button');
if (!btn || !list.contains(btn)) return; // клик не по кнопке внутри #users
const action = btn.dataset.action; // edit | delete
const item = btn.closest('li');
if (action === 'edit') {
console.log('Редактируем:', item.textContent.trim());
}
if (action === 'delete') {
item.remove();
}
});
// Новые элементы автоматически поддерживаются делегированием
list.insertAdjacentHTML('beforeend', `<li>Клара <button data-action="edit">Редактировать</button> <button data-action="delete">Удалить</button></li>`);
Мы повесили только один обработчик на #users. Любые новые кнопки внутри списка начнут работать без дополнительного кода — именно поэтому делегирование идеально подходит для динамических интерфейсов.
Как это работает: target и currentTarget
list.addEventListener('click', (e) => {
console.log('target:', e.target); // исходный узел (например, <span> внутри кнопки)
console.log('currentTarget:', e.currentTarget); // сам <ul id="users">
});
event.target — где произошел клик. event.currentTarget — элемент, на котором висит обработчик. Для безопасной фильтрации лучше использовать closest и matches.
Фильтрация по селектору: безопаснее и читабельнее
list.addEventListener('click', (e) => {
const actionBtn = e.target.closest('[data-action]');
if (!actionBtn || !list.contains(actionBtn)) return;
if (actionBtn.matches('[data-action="edit"]')) {
// ...
} else if (actionBtn.matches('[data-action="delete"]')) {
// ...
}
});
closest поднимается вверх по DOM, поэтому срабатывает даже если кликнули по вложенному узлу внутри кнопки.
Делегирование не только кликов: фокус, ввод, контекстное меню
Не все события всплывают одинаково: например, focus и blur не всплывают, но есть аналоги — focusin и focusout. Либо можно использовать режим захвата.
const form = document.querySelector('#profile');
// Вариант 1: всплывающие аналоги
form.addEventListener('focusin', (e) => {
const input = e.target.closest('input, textarea');
if (input) input.classList.add('focused');
});
form.addEventListener('focusout', (e) => {
const input = e.target.closest('input, textarea');
if (input) input.classList.remove('focused');
});
// Вариант 2: режим захвата (capture)
form.addEventListener('focus', (e) => {
// ...
}, { capture: true });
Аналогично можно делегировать change, contextmenu, keydown, input и другие события, учитывая их особенности.
Производительность: почему делегирование быстрее
- Меньше обработчиков — меньше памяти и затрат на регистрацию.
- Не нужно перевешивать обработчики при перерисовке списка.
- Меньше закрытий и ссылок, которые мог бы удерживать сборщик мусора.
Для списков на сотни и тысячи элементов делегирование почти всегда выигрывает. Исключения — когда каждый элемент требует уникальную тяжелую логику, привязанную к замыканиям — но такое встречается редко.
Частые ошибки и как их избежать
- Остановка всплытия в дочерних узлах. Если где-то внутри вызывается
e.stopPropagation(), делегирование выше не сработает. ИспользуйтеstopPropagationтолько когда понимаете последствия. - Неправильная проверка цели.
e.targetможет быть вложенным элементом (например, <svg> или <span>). Всегда применяйтеclosestи проверяйте принадлежность контейнеру. - Смешивание inline-обработчиков и делегирования. Старайтесь придерживаться одного стиля — это упростит отладку.
- Злоупотребление
innerHTML. Полная замена содержимого контейнера уничтожит состояние (например, выделение, фокус). По возможности меняйте только нужные узлы.
Мини-практика: делегированный ToDo-лист
<form id="todo-form">
<input name="title" placeholder="Новая задача" required>
<button>Добавить</button>
</form>
<ul id="todo"></ul>
const formEl = document.getElementById('todo-form');
const listEl = document.getElementById('todo');
formEl.addEventListener('submit', (e) => {
e.preventDefault();
const title = new FormData(formEl).get('title');
listEl.insertAdjacentHTML('beforeend',
`<li>
<label>
<input type="checkbox" data-action="toggle">
<span class="text">${title}</span>
</label>
<button data-action="remove">×</button>
</li>`
);
formEl.reset();
});
listEl.addEventListener('click', (e) => {
const removeBtn = e.target.closest('[data-action="remove"]');
if (removeBtn) {
removeBtn.closest('li').remove();
return;
}
});
listEl.addEventListener('change', (e) => {
const checkbox = e.target.closest('[data-action="toggle"]');
if (!checkbox) return;
checkbox.closest('li').classList.toggle('done', checkbox.checked);
});
У нас один обработчик на клик и один на изменение — список может расти бесконечно, но код не усложняется.
Что дальше изучить
- addEventListener: опции once, passive, capture
- Поведение редких событий: mouseenter vs mouseover
- Делегирование на уровне документа: модальные окна, контекстные меню, хоткеи
Если хотите закрепить паттерны на практике и собрать мини‑проекты с нуля до продвинутого уровня, рекомендую пройти интенсив: Пройти практический курс «JavaScript с Нуля до Гуру 2.0» — там много упражнений и разборов кода с обратной связью.
Итоги
Делегирование событий в JavaScript — простой способ ускорить приложение, снизить расход памяти и облегчить работу с динамическим DOM. Запомните три правила: вешайте обработчик на общий контейнер, фильтруйте цель через closest, учитывайте особенности всплытия разных событий. Этого достаточно, чтобы уверенно применять прием в реальных проектах.
-
Создано 05.12.2025 20:28:02
-
Михаил Русаков

Комментарии (0):
Для добавления комментариев надо войти в систему.
Если Вы ещё не зарегистрированы на сайте, то сначала зарегистрируйтесь.