Как три фазы событий в JavaScript помогают управлять обработчиками
Что проверяют
Понимаете ли вы путь события по DOM и умеете ли строить на нём решения: делегирование, перехват раньше потомков, остановку распространения. Следом часто просят объяснить разницу между stopPropagation и preventDefault или показать делегирование на списке.
Как отвечать
Событие проходит три фазы, их номер лежит в event.eventPhase:
- захват (1): от
windowвниз черезdocumentи предков к цели. Слушатель срабатывает здесь, если добавлен с{ capture: true }или третьим аргументомtrue; - цель (2): событие на самом элементе. В современных браузерах сначала вызываются capture-слушатели цели, потом обычные;
- всплытие (3): обратно вверх до
window. По умолчаниюaddEventListenerвешает обработчик именно сюда.
Что это даёт на практике:
- делегирование: один обработчик на
ulвместо сотни наli, нужный элемент ищется черезevent.target.closest('li'). Работает и для элементов, добавленных позже; - перехват раньше потомков: capture-слушатель на контейнере видит клик до обработчиков внутри, так делают закрытие выпадающих меню и сбор аналитики;
- управление распространением:
stopPropagation()обрывает путь дальше,stopImmediatePropagation()ещё и не даёт сработать другим слушателям на том же элементе.preventDefault()отменяет действие браузера и распространение не трогает.
Упомяните, что всплывают не все события: focus, blur, mouseenter и mouseleave не всплывают, для делегирования берут focusin и focusout. И что React с 17-й версии вешает свои обработчики на корневой контейнер, а для фазы захвата у него пропсы вида onClickCapture.
Где спотыкаются
Путают event.target и event.currentTarget. Первое это элемент, где событие возникло, второе это элемент, на котором висит текущий обработчик.
Лечат баги через stopPropagation где попало. Это ломает делегирование выше по дереву, например закрытие модалки кликом снаружи.
Снимают слушатель через removeEventListener без того же флага capture. Добавленный с capture: true снимается только с ним, иначе продолжает срабатывать.