Делегування подій (Event Delegation)
Делегування подій це прийом у JavaScript, коли ми вішаємо обробник не на кожен елемент, а на їхнього спільного батька, і використовуємо спливання подій, щоб визначити, на якому саме елементі сталася дія. Іншими словами: один слухач керує подіями багатьох дочірніх елементів.
Теорія
TL;DR
- Один обробник на батьківському елементі замість сотні обробників на дітях.
- Працює завдяки спливанню: подія від дитини піднімається до батька, де її й ловлять.
- Конкретний елемент визначається через
event.target, зазвичай за допомогоюmatches()абоclosest(). - Автоматично охоплює елементи, додані в DOM пізніше.
- Не працює для подій, які не спливають (
focus,blur,mouseenter,mouseleave).
Швидкий приклад
Треба обробити кліки по 100 кнопках.
Наївно, слухач на кожній кнопці:
document.querySelectorAll('button').forEach(btn => {
btn.addEventListener('click', () => {
console.log('clicked');
});
});Правильно, делегування:
document.body.addEventListener('click', (event) => {
if (event.target.matches('button')) {
console.log('clicked');
}
});Навіщо це потрібно
Ключові тези, які варто назвати на співбесіді:
- Менше обробників, отже менше пам'яті і швидша робота сторінки.
- Працює для динамічно доданих елементів: навіть якщо елемент з'явився пізніше, батьківський обробник його все одно «спіймає».
- Краща керованість: уся логіка в одному місці, а не розмазана по дереву DOM.
- Простіше прибирання: щоб від'єднати обробку, достатньо зняти один слухач, а не обходити всі вузли.
Як це працює
- Користувач клікає по кнопці.
- Подія спливає вгору по дереву DOM.
- Обробник на
body(або на будь-якому спільному предку) перехоплює подію. - Ми перевіряємо
event.target, щоб визначити елемент-кандидат.
Надійніший варіант перевірки, коли всередині кнопки є вкладена розмітка (іконка, <span>):
list.addEventListener('click', (event) => {
const item = event.target.closest('li');
if (!item || !list.contains(item)) return;
console.log('Клік по пункту:', item.dataset.id);
});closest() піднімається від фактичної цілі вгору, поки не знайде відповідний елемент, тож клік по іконці всередині пункту теж спрацює правильно.
target проти currentTarget
Це найчастіша помилка на співбесіді:
event.targetце елемент, по якому клікнули, тобто джерело події.event.currentTargetце елемент, на якому висить слухач, тобто батько.
| Властивість | На що вказує | У делегуванні |
|---|---|---|
event.target | Реальна ціль події | Дочірній елемент, який нас цікавить |
event.currentTarget | Елемент зі слухачем | Батьківський контейнер |
Усередині звичайної функції-обробника this дорівнює event.currentTarget, а не target.
Коли делегування не підходить
- Подія не спливає. Для
focusіblurберітьfocusinіfocusout, дляmouseenterіmouseleaveберітьmouseoverіmouseout. - Хтось на шляху викликав
stopPropagation(). Тоді подія просто не дійде до батьківського слухача, і делегування мовчки перестане працювати. - Дуже гарячі події на великому контейнері. Для
mousemoveчиscrollодин обробник наdocumentможе стати вузьким місцем, там краще точкові слухачі або throttling.
Коротке визначення, якщо інтерв'юер просить стисло: делегування подій це техніка, коли один обробник на батьківському елементі обробляє події багатьох дочірніх елементів, використовуючи спливання подій.
Типові помилки
- Плутати
targetіcurrentTarget, а потім дивуватися, чому перевірка завжди дає батьківський контейнер. - Перевіряти лише
event.target.matches(...)там, де ціль може бути вкладеним вузлом. Використовуйтеclosest(). - Вішати слухач одразу на
documentбез потреби. Беріть найближчого спільного предка: менше зайвих спрацювань і менше конфліктів з іншим кодом. - Забувати перевірити, що знайдений елемент справді всередині контейнера. Без
container.contains(item)можна зловити збіг із чужого блока. - Розраховувати на делегування для подій, які не спливають, і потім довго шукати, чому обробник не викликається.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.