Простое объяснение делегирования событий

от автора

Приветствую. Представляю вашему вниманию перевод статьи «A Simple Explanation of Event Delegation in JavaScript», опубликованной 14 июля 2020 года автором Dmitri Pavlutin

В данной статье Дмитрий Павлутин объясняет, на чём основан один из базовых паттернов работы с DOM-событиями.

1. Почему делегирование событий?

Давайте напишем скрипт, который при нажатии на HTML-кнопку, будет отправлять сообщение в консоль.

Чтобы срабатывало такое поведение, необходимо в JavaScript найти эту кнопку и с помощью метода addEventListener() прикрепить к ней обработчик события.

<button id="buttonId">Click me</button>  <script>   document.getElementById('buttonId')     .addEventListener('click', () => console.log('Clicked!')); </script>

Данный способ позволяет начать отслеживать события на одном элементе. Например, на кнопке.

А что, если возникает необходимость отслеживать события на множестве кнопок? Вот пример одного из способов реализаций:

<div id="buttons">   <button class="buttonClass">Click me</button>   <button class="buttonClass">Click me</button>   <!-- Кнопки... -->   <button class="buttonClass">Click me</button> </div>  <script>   const buttons = document.getElementsByClassName('buttonClass');   for (const button of buttons) {     button.addEventListener('click', () => console.log('Clicked!'));   } </script>

Посмотреть, как работает данный способ, можно в демонстрации CodeSandbox

Сначала делается выборка всех необходимых кнопок страницы, затем с помощью цикла for (const button of buttons) производится обход всех элементов этого списка, в котором к каждой кнопке прикрепляется обработчик события. Также, когда во время работы с документом на странице появляются новые кнопки, возникает необходимость вручную прикреплять обработчики событий для этих новых элементов.

Существует ли лучший способ?

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

Делегирование событий использует особенности работы "распространения событий". Чтобы понять, как работает делегирование, предлагаю сначала разобраться в принципе работы их распространения.

2. Распространение событий

Когда вы нажимаете на кнопку в следующей HTML-разметке:

<html>   <body>     <div id="buttons">       <button class="buttonClass">Click me</button>     </div>   </body> </html>

На каком количестве элементов сработает событие click? Без сомнений, событие клика получит сама кнопка. Но помимо неё, такое же событие получит и вся цепочка элементов, являющихся её предками (даже объекты document и window).

Событие клика распространяется в 3 этапа:

  1. Фаза захвата / погружения (capturing phase) – начиная с window, document и корневого элемента, событие погружается сверху вниз по DOM-дереву через предков целевого элемента, на котором произошло событие
  2. Фаза цели (target phase) – срабатывание соыбытия на элементе, на который пользователь кликнул
  3. Фаза всплытия (bubble phase) – наконец, событие всплывает по цепочке предков целевого элемента, пока не достигнет корневого элемента, а затем объектов document и window

Третий аргумент captureOrOptions метода addEventListener:

element.addEventListener(eventType, handler[, captureOrOptions]);

позволяет вам перехватывать события на разных этапах их распространения.

  • Если аргумент captureOrOptions пропущен, имеет значение false или `{ capture: false }, обработчик будет захватывать события на "Фазе цели" и "Фазе всплытия"
  • Если же аргумент captureOrOptions имеет значение true или `{ capture: true }, обработчик сработает уже на "Фазе захвата (погружения)"

В следующем примере обработчик перехватывает событие клика на элементе <body> на "Фазе захвата":

document.body.addEventListener('click', () => {   console.log('Body click event in capture phase'); }, true);

В демонстрации CodeSandbox, при нажатии на кнопку, в консоли можно увидеть, как распространяется событие.

Итак, как распространение события помогает перехватывать события из множества кнопок?

Принцип прост: обработчик события прикрепляется к элементу, являющемуся для кнопок родительским, и при нажатии на кнопку отлавливает всплывающее событие. Именно так работает делегирование событий.

3. Делегирование событий

Давайте воспользуемся делегированием, чтобы отловить клики на несколько кнопок:

<div id="buttons"> <!-- Шаг 1 -->   <button class="buttonClass">Click me</button>   <button class="buttonClass">Click me</button>   <!-- Кнопки... -->   <button class="buttonClass">Click me</button> </div>  <script>   document.getElementById('buttons')     .addEventListener('click', event => { // Step 2       if (event.target.className === 'buttonClass') { // Step 3         console.log('Click!');       }     }); </script>

Откройте демонстрационный код и кликните на любую кнопку – вы увидите в консоли сообщение "Click!".

Идея делегирования событий роста. Вместо прикрепления обработчиков событий прямо к кнопкам, мы делегируем отслеживание этого события родительскому элементу <div id="buttons">. Когда нажимается кнопка, обработчик, назначенный родительскому элементу ловит всплывающее событие (помните раздел про распространение событий?).

Использование делегирования событий требует 3 шагов:

Шаг 1. Определить общего родителя элементов для отслеживания событий
В примере ниже <div id="buttons"> является общим родителем для кнопок.

Шаг 2. Прикрепить к родительскому элементу обработчик событий
document.getElementById('buttons').addEventListener('click', handler) прикрепляет обработчик событий к родителю кнопок. Этот обработчик также реагирует на нажатия на кнопки, так как события нажатий на кнопки всплывают по всем элементам-предкам (благодаря распространению событий).

Шаг 3. Использовать event.target для выбора целевого элемента
Когда кнопка нажата, функция-обработчик вызывается с аргументом: объектом event. Свойство event.target обращается к элементу, на котором произошло событие (в нашем примере этот элемент – кнопка):

  // ...   .addEventListener('click', event => {     if (event.target.className === 'buttonClass') {       console.log('Click!');     }   });

Кстати, на элемент к которому прикреплён сработавший обработчик события, указывает event.currentTarget. В нашем примере event.currentTarget указывает на элемент <div id="buttons">.

Теперь вы можете увидеть преимущества шаблона делегирования событий: вместо прикрепления обработчиков к каждой кнопке, как это было сделано раньше, благодаря делегированию событий, остаётся потребность только в одном обработчике.

4. Резюме

Когда происходит событие нажатия на кнопку (или любое другое распространяющееся событие):

  • Сначала событие опускается вниз от window, document, корневого элемента и через всех предков целевого элемента (фаза захвата / погружения)
  • Событие происходит на целевом элемента (фаза цели)
  • И наконец, событие всплывает вверх через элементы, являющиеся предками, пока не достигнет корневого элемента, document и window (фаза всплытия)

Механизм называется распространением события.

Делегирование событий является полезным шаблоном, так как позволяет отслеживать события на множестве элементов с помощью только одного обработчика.

Для работы делегирования событий нужно 3 шага:

  1. Определить родителя элементов для отслеживания событий
  2. Прикрепить на него обработчик событий
  3. Использовать event.target для выбора целевого элемента

ссылка на оригинал статьи https://habr.com/ru/post/512782/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *