Зарегистрируйтесь для доступа к 15+ бесплатным курсам по программированию с тренажером

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

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 Stages

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

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

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

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


Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты

Об обучении на Хекслете

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

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

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

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

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

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

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

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы
профессия
от 6 300 ₽ в месяц
Разработка фронтенд-компонентов для веб-приложений
10 месяцев
с нуля
Старт 21 марта
профессия
от 9 900 ₽ в месяц
Разработка фронтенд- и бэкенд-компонентов для веб-приложений
16 месяцев
с нуля
Старт 21 марта
профессия
новый
Автоматизированное тестирование веб-приложений на JavaScript
8 месяцев
c опытом
в разработке
Старт 21 марта

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»