Как работает всплытие и захват событий в JavaScript и какой обработчик сработает первым на div и кнопке
Что проверяют
Знаете ли вы три фазы распространения события и умеете ли предсказать порядок вывода. Обычно это задача с кодом: div с кнопкой внутри, несколько обработчиков и вопрос «что напечатается». Следом спрашивают про stopPropagation и делегирование.
Как отвечать
Событие проходит путь в три фазы:
- захват (capturing): от
windowвниз по предкам к цели. Здесь срабатывают обработчики, добавленные с{ capture: true }или третьим аргументомtrue; - цель (target): обработчики на самом элементе, по которому кликнули;
- всплытие (bubbling): обратно вверх от цели к
window. Здесь срабатывают обычные обработчики, такaddEventListenerведёт себя по умолчанию.
При клике по кнопке порядок такой: захватывающий обработчик div, затем обработчики кнопки, затем всплывающий обработчик div. Если на div нет обработчика с capture, первой сработает кнопка. При клике по самому div мимо кнопки её обработчики не вызовутся вовсе.
Чтобы ответ был полным, добавьте:
targetиcurrentTarget: первый это элемент, где случилось событие, второй это элемент, чей обработчик выполняется сейчас;stopPropagation(): обрывает дальнейший путь, аstopImmediatePropagation()вдобавок не даёт сработать остальным обработчикам того же элемента;- делегирование: один обработчик на родителе и проверка
event.target.closest('button')вместо сотни обработчиков на элементах списка; - не всплывают:
focus,blur,mouseenter,mouseleave. Для делегирования фокуса естьfocusinиfocusout.
Во фреймворках это тоже видно: React вешает обработчики на корневой контейнер, а для фазы захвата даёт onClickCapture.
Где спотыкаются
Путают направление и говорят, что захват идёт от цели вверх. Захват всегда идёт сверху вниз.
Считают, что stopPropagation() отменяет действие браузера по умолчанию. Для этого нужен preventDefault(), это разные вещи.
Забывают, что при снятии обработчика нужен тот же флаг: removeEventListener('click', fn) не снимет обработчик, добавленный с capture: true.