Распространение. Всплытие. Делегирование
Last updated
Last updated
Распространение событий (event propagation) — важная, но непонятная тема, когда речь идет о событиях. Это всеобъемлющий термин, который включает в себя три разных этапа жизни события: затопление, таргетинг и всплытие.
Распространение события двунаправленно — оно начинается на window
, идет к целевому элементу и заканчивается на window
. Распространение часто неправильно используется как синоним стадии всплытия. Каждый раз, когда происходит событие, происходит его распространение.
При наступлении события, оно проходит через три обязательные фазы:
Capture phase — событие начинается на window
и тонет (проходит через все узлы-предки ) до самого глубокого целевого элемента где произошло событие.
Target phase — событие дошло до самого глубокого целевого элемента. Этот этап включает только уведомление узла на котором произошло событие.
Bubbling phase — заключительная фаза, событие всплывает от самого глубокого, целевого элемента, через все узлы-предки до window
.
Основной принцип всплытия — при наступлении события, обработчики сначала срабатывают на самом вложенном элементе, затем на его родителе, затем выше и так далее, вверх по цепочке вложенности. Всплывают почти все события, например события focus
и blur
не всплывают.
Давайте рассмотрим пример, так будет понятнее. Есть три вложенных div
, с обработчиками клика на каждом. Всплытие гарантирует, что клик по внутреннему inner-child
вызовет обработчик клика, если есть, сначала на самом inner-child
, затем на элементе child
, далее на элементе parent
и так далее вверх по цепочке родителей до window
. Поэтому если в примере кликнуть на inner-child
, то последовательно выведутся alert: inner-child → child → parent
.
Этот процесс называется всплытием (event bubbling), потому что события всплывают от внутреннего элемента вверх через предков, подобно тому, как всплывает пузырек воздуха в воде.
Целевой элемент — на каком бы элементе мы ни поймали событие, всегда можно узнать где конкретно оно произошло. Самый глубокий элемент, который вызывает событие, называется целевым или исходным и доступен как event.target
.
Отличия event.target
и event.currentTarget
:
event.target
— это ссылка на исходный элемент на котором произошло событие, в процессе всплытия он неизменен.
event.currentTarget
— это текущий элемент до которого дошло всплытие, на нём сейчас выполняется обработчик.
Если стоит только один обработчик на самом верхнем элементе, то он поймает все клики внутри родителя. Где бы ни был клик внутри, он всплывёт до элемента-родителя на котором сработает обработчик.
Обычно событие будет всплывать наверх до элемента window
, вызывая все обработчики на своем пути. Но любой промежуточный обработчик может решить, что событие полностью обработано и остановить всплытие. Остановить всплытие можно, вызвав метод на объекте события.
Если у элемента есть несколько обработчиков на одно событие, то даже при прекращении всплытия все они будут выполнены. То есть, stopPropagation
препятствует продвижению события дальше, но на текущем элементе все обработчики выполнятся.
Используется для того, чтобы полностью остановить обработку события. Он не только предотвращает всплытие, но и останавливает обработку событий на текущем элементе.
Не прекращайте всплытие без необходимости, всплытие – это удобно! Прекращение всплытия создаёт свои подводные камни, которые потом приходится обходить.
Всплытие событий позволяет реализовать один из самых важных приёмов разработки — делегирование событий. Он заключается в том, что если есть много элементов, события которых нужно обрабатывать похожим образом, то вместо того, чтобы назначать обработчик каждому, мы ставим один обработчик на их общего предка. Из него можно получить целевой элемент event.target
, понять на каком именно потомке произошло событие и обработать его.
Делегирование (event delegation) — это средство оптимизации интерфейса. Мы используем один обработчик для схожих действий на однотипных элементах.
Рассмотрим делегирование на примере. Создаем элемент div
, добавляем в него двести параграфов и вешаем слушатели событий с функцией respondToTheClick
к каждому параграфу.
Проблема в том, что у нас есть двести слушателей событий. Все они указывают на одну и ту же функцию слушателя, но самих слушателей 200! Что если мы переместим всех слушателей на общего родителя? 🤔
Теперь есть только один слушатель и браузеру не нужно хранить в памяти двести различных функций. Это - существенная оптимизация. Теперь для доступа к элементу на котором произошло событие, используем свойство target
на объекте события в котором хранится ссылка на целевой элемент.
При клике в параграф происходит приблизительно следующее:
Произошел клик в элемент параграфа
Событие проходит этап захвата (capturing phase) и достигает цели
Теперь наступает фаза всплытия и событие поднимается по иерархии DOM-дерева
Когда событие всплывает до div
, срабатывает слушатель и вызывается функция respondToTheClick
Внутри callback-функции, event.target
— это элемент, на котором произошел клик. Таким образом, мы получаем прямой доступ к целевому элементу, абзацу, и можем работать с ним.
Такой подход имеет ряд преимуществ.
Упрощает инициализацию и экономит память — не нужно вешать много обработчиков.
Меньше кода — при добавлении и удалении элементов не нужно ставить или снимать обработчики.
Удобство изменений — можно массово добавлять или удалять элементы изменения, при этом на добавленных элементах будет весь функционал.
Будем делать подсветку активного элемента навигации при клике. Вместо того, чтобы назначать обработчик каждому элементу навигации, которых может быть много, повесим единый обработчик на список. Используем event.target
, чтобы получить элемент на котором произошло событие, и подсветить его.
Обязательно проверяем цель клика, чтобы это точно был элемент ссылки, мы не хотим подсвечивать весь список или его элементы, только ссылки. Для проверки типа узла используем свойство nodeName
.
Мы рассмотрим фазу всплытия, так как она наиболее часто используемая и полезная. Для ознакомления с остальными фазами и более детальной информации прочитайте .