Предположим, что у нас есть несколько элементов, на каждом из которых висит обработчик события click
. Один элемент внешний — div
. Внутри него находятся два элемента с кнопками:
<div>
<button id="send">Send</button>
<button id="cancel">Cancel</button>
</div>
const div = document.querySelector('div');
const button1 = document.querySelector('#send');
const button2 = document.querySelector('#cancel');
div.addEventListener('click', () => alert('Div alert'));
button1.addEventListener('click', () => alert('Button Send alert'));
button2.addEventListener('click', () => alert('Button Cancel alert'));
Если выполнить щелчок по области внешнего элемента, то выполнится обработчик, привязанный к этому внешнему элементу.
Если выполнить щелчок по внутреннему элементу, то автоматически выполнится щелчок и по внешнему элементу. Значит, отработают оба события.
Возникает закономерный вопрос: «В каком порядке выполнятся эти события после щелчка на кнопку?». В общем случае событие проходит сквозь дерево от корня до самого глубокого элемента, на котором событие сработало, а затем в обратном направлении. Путешествие события туда и обратно называется его стадиями или фазами, ниже поговорим о них подробнее.
Погружение (Capturing)
Когда событие только возникло, оно начинает двигаться по DOM-дереву от корневого узла до самого глубокого, на котором произошло событие:
| |
---------------| |---------------
| div | | |
| -----------| |----------- |
| | button \ / | |
| ------------------------- |
| Event CAPTURING |
---------------------------------
Попутно на стадии погружения будут выполнены обработчики, которые были привязаны к этой стадии. Привязка регулируется третьим параметром функции addEventListener
:
div.addEventListener('click', () => alert('Div alert'), true);
button1.addEventListener('click', () => alert('Button Send alert'), true);
button2.addEventListener('click', () => alert('Button Cancel alert'), true);
В примере выше пользователь кликнул по кнопке Send
, которая находится внутри элемента div
. Значение true
привязывает обработчики к стадии погружения. Событие срабатывает вниз по дереву от корневого узла. Корневым узлом является div
, срабатывает обработчик этого события на этом элементе. Затем событие переходит дальше к дочернему элементу, на котором произошло событие и вызывается обработчик события этого элемента. В нашем случае это кнопка Send
. Получится такой вывод:
Div alert
Button Send alert
Обратите внимание, что обработчик кнопки Cancel
не был вызван, так как событие произошло не на этой кнопке.
See the Pen event_stages-1 by Hexlet (@hexlet) on CodePen.
Всплытие (Bubbling)
После остановки погружения на элементе target
, начинается всплытие:
/ \
---------------| |---------------
| div | | |
| -----------| |----------- |
| | button | | | |
| ------------------------- |
| Event BUBBLING |
---------------------------------
Именно эта стадия подразумевается при вызове addEventListener
без третьего параметра:
div.addEventListener('click', () => alert('Div alert'));
button1.addEventListener('click', () => alert('Button Send alert'));
button2.addEventListener('click', () => alert('Button Cancel alert'));
На ней выполнение обработчиков происходит изнутри наружу, поэтому порядок вывода сообщений меняется:
Button Send alert
Div alert
See the Pen event_stages-1 by Hexlet (@hexlet) on CodePen.
Всплытие событий — это важная часть поведения DOM. Без него было бы невозможно реализовать события, которые срабатывают на целых блоках, а не только самых глубоких элементах. Самый простой пример — контекстное меню.
Другой пример — таблицы, устроенные по принципу Excel. Эти таблицы огромны. Добавление событий на каждую ячейку привело бы к созданию большого числа одинаковых обработчиков, которые нужно постоянно добавлять с ростом таблицы. Кроме дополнительного кода, такая схема еще и тормозит на больших объемах. Гораздо проще повесить один обработчик на всю таблицу. В примере ниже добавлен обработчик клика на таблицу. При этом он будет срабатывать при нажатии на любую из кнопок, которые находятся внутри таблицы.
See the Pen Untitled by Hexlet (@hexlet) on CodePen.
W3C Модель
Большинство событий проходят обе стадии, сначала погружаясь в глубину дерева и затем поднимаясь до самого верха. Стадия погружения используется редко, большая часть обработчиков вешается на стадию всплытия.
В предыдущем уроке мы познакомились с объектом e.target
. Это самый глубокий элемент, до которого идет погружение. В процессе всплытия target
не меняется. Благодаря ему всегда можно узнать, где конкретно произошло событие.
Кроме него, доступен объект currentTarget
— это элемент, к которому прикреплен данный обработчик. В зависимости от ситуации используется тот или иной:
В обычной ситуации событие должно всплывать до конца, но иногда могут возникать ситуации, когда всплытие нежелательно.
Можно его остановить двумя способами:
event.stopPropagation()
— останавливает всплытие, но позволяет доработать всем обработчикам, которые висят на текущем элементеevent.stopImmediatePropagation()
— останавливает всплытие вместе со всеми обработчиками
Подведем итог. Перехват — это механизм передачи события обработчику. При погружении или всплытии событие переходит от одного элемента другому. Если в элементе есть обработчик этого события, то он вызывается — происходит перехват события обработчиком. Используя описанные выше методы, мы можем остановить процесс передачи события между элементами.
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
- Статья «Как учиться и справляться с негативными мыслями»
- Статья «Ловушки обучения»
- Статья «Сложные простые задачи по программированию»
- Вебинар «Как самостоятельно учиться»
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.