Зарегистрируйтесь, чтобы продолжить обучение

Перехват и всплытие JS: DOM API

Предположим, что у нас есть несколько элементов, на каждом из которых висит обработчик события 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 не был вызван, так как событие произошло не на этой кнопке.

https://codepen.io/hexlet/pen/KKbmQMv

Всплытие (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

https://codepen.io/hexlet/pen/QWzvQKv

Всплытие событий — это важная часть поведения DOM. Без него было бы невозможно реализовать события, которые срабатывают на целых блоках, а не только самых глубоких элементах. Самый простой пример — контекстное меню.

Другой пример — таблицы, устроенные по принципу Excel. Эти таблицы огромны. Добавление событий на каждую ячейку привело бы к созданию большого числа одинаковых обработчиков, которые нужно постоянно добавлять с ростом таблицы. Кроме дополнительного кода, такая схема еще и тормозит на больших объемах. Гораздо проще повесить один обработчик на всю таблицу. В примере ниже добавлен обработчик клика на таблицу. При этом он будет срабатывать при нажатии на любую из кнопок, которые находятся внутри таблицы.

https://codepen.io/hexlet/pen/PoXKYor

W3C Модель

Большинство событий проходят обе стадии, сначала погружаясь в глубину дерева и затем поднимаясь до самого верха. Стадия погружения используется редко, большая часть обработчиков вешается на стадию всплытия.

В предыдущем уроке мы познакомились с объектом e.target. Это самый глубокий элемент, до которого идет погружение. В процессе всплытия target не меняется. Благодаря ему всегда можно узнать, где конкретно произошло событие.

Кроме него, доступен объект currentTarget — это элемент, к которому прикреплен данный обработчик. В зависимости от ситуации используется тот или иной:

Event Stages

В обычной ситуации событие должно всплывать до конца, но иногда могут возникать ситуации, когда всплытие нежелательно.

Можно его остановить двумя способами:

  • event.stopPropagation() — останавливает всплытие, но позволяет доработать всем обработчикам, которые висят на текущем элементе
  • event.stopImmediatePropagation() — останавливает всплытие вместе со всеми обработчиками

Подведем итог. Перехват — это механизм передачи события обработчику. При погружении или всплытии событие переходит от одного элемента другому. Если в элементе есть обработчик этого события, то он вызывается — происходит перехват события обработчиком. Используя описанные выше методы, мы можем остановить процесс передачи события между элементами.

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff