← Назад к вопросам
Как поймать событие на стадии Capturing?
1.7 Middle🔥 112 комментариев
#JavaScript Core
Комментарии (2)
🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Ловля событий на стадии Capturing
Это важный вопрос о механизме распространения событий (Event Bubbling и Capturing). Разберу в деталях.
Как работает распространение событий
В браузере событие проходит три фазы:
1. CAPTURING - от корня документа к целевому элементу
2. TARGET - событие достигает целевого элемента
3. BUBBLING - от целевого элемента вверх к корню
Полный путь: window -> document -> html -> body -> ... -> target -> ... -> body -> html -> document -> window
Использование третьего параметра addEventListener
// Синтаксис
element.addEventListener(event, listener, useCapture);
// useCapture = true -> ловим на фазе CAPTURING
// useCapture = false -> ловим на фазе BUBBLING (по умолчанию)
Пример: ловим на фазе Capturing
// HTML
// <div class="parent">
// <div class="child">
// <button>Нажми</button>
// </div>
// </div>
const button = document.querySelector('button');
const parent = document.querySelector('.parent');
// Слушатель на CAPTURING фазе
parent.addEventListener('click', (e) => {
console.log('CAPTURING: родитель получит событие ПЕРВЫМ');
}, true); // true = используем capturing
// Слушатель на BUBBLING фазе (стандарт)
button.addEventListener('click', (e) => {
console.log('TARGET: событие достигло кнопки');
}, false); // false = bubbling (по умолчанию)
// Слушатель на BUBBLING фазе
parent.addEventListener('click', (e) => {
console.log('BUBBLING: родитель получит событие ПОСЛЕДНИМ');
}, false);
Порядок логирования при клике на button:
1. CAPTURING: родитель получит событие ПЕРВЫМ
2. TARGET: событие достигло кнопки
3. BUBBLING: родитель получит событие ПОСЛЕДНИМ
Более сложный пример
const grandparent = document.querySelector('.grandparent');
const parent = document.querySelector('.parent');
const child = document.querySelector('.child');
// Capturing слушатели (true)
grandparent.addEventListener('click', () => {
console.log('Grandparent CAPTURING');
}, true);
parent.addEventListener('click', () => {
console.log('Parent CAPTURING');
}, true);
// Target слушатели (нет разницы false/true для целевого элемента)
child.addEventListener('click', () => {
console.log('Child TARGET');
}, true);
child.addEventListener('click', () => {
console.log('Child TARGET (second)');
}, false);
// Bubbling слушатели (false)
parent.addEventListener('click', () => {
console.log('Parent BUBBLING');
}, false);
grandparent.addEventListener('click', () => {
console.log('Grandparent BUBBLING');
}, false);
Вывод при клике на child:
Grandparent CAPTURING
Parent CAPTURING
Child TARGET
Child TARGET (second)
Parent BUBBLING
Grandparent BUBBLING
Управление распространением
const element = document.querySelector('button');
element.addEventListener('click', (event) => {
// Остановить capturing и bubbling полностью
event.stopPropagation();
// Остановить только bubbling (capturing продолжится)
// event.stopImmediatePropagation();
console.log('Событие не распространится дальше');
}, true); // capturing phase
React пример
В React ситуация сложнее из-за synthetic events:
export function EventCapturingExample() {
const handleParentCapturing = (e) => {
// В React нет встроенной поддержки capturing
// Нужно использовать нативный API через useEffect
console.log('Parent bubbling (React стандартно)');
};
// Правильный способ в React - использовать нативный addEventListener
const parentRef = useRef(null);
const childRef = useRef(null);
useEffect(() => {
const parent = parentRef.current;
const child = childRef.current;
// Capturing фаза
const captureHandler = (e) => {
console.log('CAPTURING:', e.target);
};
parent?.addEventListener('click', captureHandler, true);
child?.addEventListener('click', captureHandler, true);
return () => {
parent?.removeEventListener('click', captureHandler, true);
child?.removeEventListener('click', captureHandler, true);
};
}, []);
return (
<div ref={parentRef} className="border p-4">
<button ref={childRef} onClick={handleParentCapturing}>
Click me
</button>
</div>
);
}
Когда использовать Capturing
Capturing полезен для:
- Event delegation с приоритетом - перехватить событие до того как его перехватит вложенный элемент
const form = document.querySelector('form');
form.addEventListener('submit', (e) => {
// Это сработает раньше, чем handler у input
console.log('Валидация на уровне формы');
e.preventDefault();
}, true);
- Остановка распространения события до того как оно достигнет целевого элемента
document.addEventListener('click', (e) => {
if (e.target.matches('.ignore-clicks')) {
e.stopPropagation();
return false;
}
}, true);
- Глобальная обработка ошибок в фазе capturing
Частые ошибки
// ❌ Забыли третий параметр
element.addEventListener('click', handler); // Это будет BUBBLING
// ✅ Правильно для capturing
element.addEventListener('click', handler, true);
// ❌ Используешь старый способ
element.onclick = handler; // Только bubbling, старо
// ✅ Правильно
element.addEventListener('click', handler, true);
Производительность
Использование capturing может быть полезно для оптимизации:
// Вместо добавления listener каждому элементу
const items = document.querySelectorAll('.item');
items.forEach(item => {
item.addEventListener('click', handler); // Много слушателей
});
// Лучше использовать capturing на родителе
const container = document.querySelector('.container');
container.addEventListener('click', (e) => {
if (e.target.matches('.item')) {
handler(e);
}
}, true); // Один слушатель на capturing фазе
Итоговая таблица
| Параметр | Фаза | Порядок | Когда использовать |
|---|---|---|---|
| true | Capturing | Раньше | Перехват до целевого элемента |
| false | Bubbling | Позже | Стандартный подход (по умолчанию) |
В большинстве случаев используешь Bubbling (false или ничего не указываешь). Capturing нужен редко, но важно его понимать для решения сложных проблем с обработкой событий.