Різниця між event.target та event.currenttarget у JavaScript
event.target - це елемент, на якому подія фактично виникла. event.currentTarget - це елемент, до якого прикріплений обробник події.
Теорія
TL;DR
event.target- звідки подія прийшла (кнопка, на яку натиснули всередині div).event.currentTarget- де живе твійaddEventListener(сам div).- Під час бабблінгу
targetне змінюється;currentTargetоновлюється на кожному обробнику. - Треба знати, що саме натиснули? Бери
target. Треба елемент з обробником? БериcurrentTarget. - Для делегування подій (event delegation) на динамічних списках -
target.
Швидкий приклад
<div id="parent" style="padding: 20px; background: lightblue;">
<button id="child">Click me</button>
</div>const parent = document.getElementById('parent');
parent.addEventListener('click', (event) => {
console.log(event.target.id); // "child" — натиснули кнопку
console.log(event.currentTarget.id); // "parent" — обробник тут
});Натискаєш кнопку: target вказує на кнопку, currentTarget на div. Обробник на div, але клік стався на кнопці всередині нього.
Головна різниця
Коли подія піднімається по DOM (бабблінг), event.target залишається прив'язаним до елемента, де подія виникла. А event.currentTarget оновлюється на кожному кроці і завжди відображає елемент, чий обробник зараз виконується. Якщо у тебе вкладені div-и з обробниками на кожному, target буде однаковим у всіх, а currentTarget у кожного свій.
Коли що використовувати
- Динамічні дочірні елементи (делегування подій): використовуй
target. Прикріплюєш один обробник на батьківський<ul>і перевіряєшe.target, щоб знайти конкретний<li>. Працює навіть для елементів, доданих після завантаження сторінки. - Власний елемент обробника: використовуй
currentTarget. Перемикання класу або читання розмірів елемента, до якого прикріплений обробник. - Більше 10 динамічних дочірніх елементів: делегування через
targetрятує від прикріплення окремих обробників до кожного. - Один елемент з обробником:
currentTargetпередбачуваний і не потребує додаткових перевірок.
Таблиця порівняння
event.target | event.currentTarget | |
|---|---|---|
| Вказує на | Елемент, де виникла подія | Елемент з прикріпленим обробником |
| Під час бабблінгу | Не змінюється | Оновлюється на кожному обробнику |
Дорівнює this? | Не завжди | Так (у звичайних функціях) |
| Використовується для | Делегування, визначення джерела | Робота з елементом-власником обробника |
Як браузер встановлює ці значення
Браузер відправляє події згідно специфікації DOM Level 2/3: фаза захоплення (capture) вниз, фаза цілі (target), фаза бабблінгу вгору. Кожного разу, коли виконується обробник, рушій встановлює currentTarget на елемент, який зареєстрував цей конкретний обробник. target встановлюється один раз при відправці і більше не змінюється. Тому після stopPropagation() target все одно залишається початковим елементом.
Типові помилки
Помилка: стилізувати target, коли потрібен елемент обробника
// Обробник на div; користувач натискає вкладений img всередині div
elem.addEventListener('click', (e) => {
e.target.style.background = 'red'; // Фарбує img, а не div
});Виправлення: e.currentTarget.style.background = 'red'.
Помилка: перемикати клас через target без перевірки
parent.addEventListener('click', (e) => {
e.target.classList.toggle('active'); // Перемикає на будь-якому дочірньому: span, p, img...
});Виправлення: додай перевірку. if (e.target.matches('button')) e.currentTarget.classList.toggle('active').
Помилка: вважати що target === currentTarget завжди
Вони рівні тільки коли натискаєш безпосередньо на елемент з обробником. Щойно клік потрапляє на дочірній елемент, вони розходяться. Це частий баг в обробниках закриття tooltip, де клік на span всередині wrapper закриває сам tooltip - бо обробник перевіряє e.target замість e.currentTarget.
Де зустрічається
- React:
SyntheticEvent.targetдля делегування форм (читанняe.target.valueу спільномуonChange).currentTargetдля доступу до самого елемента форми. - Vue:
$event.targetвсерединіv-onдля динамічного контенту слотів. - Vanilla delegation: один обробник на таблиці,
e.target.closest('tr')щоб отримати натиснутий рядок. - Міграція з jQuery: патерни
$(event.target).closest()перекладаються на vanilla черезe.target.closest().
Питання на співбесіді
Q: Що відбувається з target і currentTarget у фазі захоплення (capture)?
A: Обидва встановлюються і під час capture. currentTarget відповідає елементу, що перехоплює, а target все одно залишається початковим джерелом події.
Q: Чи змінює stopPropagation() значення target?
A: Ні. target фіксується при відправці події. stopPropagation() тільки зупиняє переміщення події до наступного елемента в ланцюжку.
Q: Чому this у звичайній функції-обробнику дорівнює currentTarget?
A: Браузер встановлює this рівним currentTarget при виклику обробника. Стрілкові функції не мають власного this, тому там цей механізм не працює.
Q: Що буде, якщо натиснути безпосередньо на елемент з обробником, без вкладених дочірніх?
A: target === currentTarget. Вони розходяться тільки коли клік відбувається на дочірньому елементі.
Q: У React Portal, на що вказує target?
A: target вказує на дочірній елемент порталу. React перерозподіляє синтетичні події через дерево React-компонентів, тому делегування працює навіть через межу порталу.
Приклади
Делегування подій на динамічному списку
const list = document.getElementById('list');
// Один обробник покриває всі елементи, включно з тими, що додаються пізніше
list.addEventListener('click', (e) => {
if (e.target.tagName === 'LI') {
console.log('Натиснуто:', e.target.textContent);
e.target.classList.toggle('selected');
}
// e.currentTarget завжди є елементом #list
});
// Цей новий елемент одразу працює — без додаткового обробника
const newItem = document.createElement('li');
newItem.textContent = 'Новий елемент';
list.appendChild(newItem);Один обробник на батьківському елементі охоплює всі пункти, поточні і майбутні. e.target повідомляє, який конкретний пункт натиснули; e.currentTarget завжди вказує на список.
Форма зі спільним обробником, який використовує обидві властивості
const form = document.getElementById('signup-form');
form.addEventListener('change', (e) => {
const field = e.target; // Яке поле змінилось
const formEl = e.currentTarget; // Елемент форми
console.log('Назва поля:', field.name);
console.log('Нове значення:', field.value);
// Перевірка всієї форми через елемент форми
const allInputs = formEl.querySelectorAll('input');
const allFilled = [...allInputs].every(i => i.value.trim() !== '');
formEl.querySelector('#submit').disabled = !allFilled;
});e.target визначає, яке поле змінилось. e.currentTarget дає доступ до всієї форми для повторної перевірки. Обидві властивості виконують різну роботу в одному обробнику.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.