По умолчанию события, инициированные элементом, распространяются вверх по дереву DOM к родителю элемента, его предкам и далее до корневого элемента (HTML).

<div>
  <span>
    <button>Click Me!</button>
  </span>
</div>

Здесь у нас есть div, который является родителем диапазона, который, в свою очередь, является родителем элемента кнопки.

Из-за всплытия событий, когда кнопка получает событие, скажем, щелчок, это событие всплывает вверх по дереву, поэтому span и div соответственно также получат событие.

Как работает делегирование событий?

С делегированием событий вместо обработки события нажатия на кнопку вы можете обрабатывать его в div.

Идея состоит в том, что вы делегируете обработку события другому элементу, а не фактическому элементу, который получил событие.

const div = document.getElementsByTagName("div")[0]

div.addEventListener("click", (event) => {
  if(event.target.tagName === 'BUTTON') {
    console.log("button was clicked")
  }
})

Объект события имеет свойство target, которое содержит информацию об элементе, фактически получившем событие. В target.tagName мы получаем имя тега для элемента и проверяем, является ли он BUTTON.

С помощью этого кода, когда вы нажимаете кнопку, событие всплывает в div, который обрабатывает событие.

Зачем использовать делегирование событий?

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

<div>
  <button>Button A</button>
  <button>Button B</button>
  <button>Button C</button>
</div>

Здесь у нас есть 3 кнопки. Допустим, мы хотели обработать событие нажатия каждой кнопки, чтобы при нажатии текст кнопки выводился в консоль.

const buttons = document.querySelectorAll('button')

buttons.forEach(button => {
  button.addEventListener("click", (event) => {
    console.log(event.target.innerText)
  })
})

Когда вы нажимаете на первую кнопку, у вас есть «Кнопка A», зарегистрированная на консоли. Для второй кнопки регистрируется «Кнопка B», а для третьей кнопки — «Кнопка C».

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

const div = document.querySelector('div')

div.addEventListener("click", (event) => {
  if(event.target.tagName === 'BUTTON') {
    console.log(event.target.innerText)
  }
})

Теперь, даже если мы добавим дополнительную кнопку, нам не придется менять код JavaScript, поскольку эта новая кнопка также разделяет родитель div (который мы делегировали для обработки событий) с другими кнопками.

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