Learning Book

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

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

Проблема: обработчик на каждый элемент

// ПЛОХО: один обработчик на каждую кнопку
const buttons = document.querySelectorAll('.btn')
buttons.forEach(btn => {
  btn.addEventListener('click', handleClick)
})
// Проблемы:
// 1. N обработчиков в памяти
// 2. Новые кнопки не будут иметь обработчика
// 3. Нужно удалять обработчики при удалении элементов

Решение: делегирование

// ХОРОШО: один обработчик на родителя
const container = document.querySelector('.buttons-container')
container.addEventListener('click', (e) => {
  // event.target — элемент, на который кликнули
  // event.currentTarget — контейнер (к которому привязан обработчик)

  if (e.target.classList.contains('btn')) {
    handleClick(e)
  }
})

// Работает и для новых кнопок, добавленных позже!
container.appendChild(newButton) // автоматически получит обработку

event.target vs event.currentTarget

document.querySelector('.list').addEventListener('click', (e) => {
  // event.target — элемент, на который реально кликнули (может быть вложенным)
  console.log('target:', e.target.tagName)

  // event.currentTarget — элемент с обработчиком (.list)
  console.log('currentTarget:', e.currentTarget.tagName)
})

// Клик на <li> внутри <ul.list>:
// target: LI
// currentTarget: UL

Метод closest() для точного делегирования

const list = document.querySelector('ul')

list.addEventListener('click', (e) => {
  // closest() ищет ближайшего предка (включая сам элемент)
  const item = e.target.closest('li')
  if (!item) return  // клик мимо li

  const deleteBtn = e.target.closest('[data-action="delete"]')
  if (deleteBtn) {
    const id = item.dataset.id
    deleteItem(id)
    return
  }

  const editBtn = e.target.closest('[data-action="edit"]')
  if (editBtn) {
    const id = item.dataset.id
    editItem(id)
    return
  }
})
closest(selector) идёт вверх по DOM-дереву от элемента и возвращает первый найденный предок (включая сам элемент), который соответствует селектору. Возвращает null если не найден.

Универсальная функция делегирования

/**
 * Делегированный обработчик событий
 * @param {Element} el - родительский элемент
 * @param {string} event - тип события
 * @param {string} selector - CSS-селектор целевых элементов
 * @param {Function} handler - обработчик
 */
function delegate(el, event, selector, handler) {
  el.addEventListener(event, function(e) {
    const target = e.target.closest(selector)
    if (target && el.contains(target)) {
      handler.call(target, e)
    }
  })
}

// Использование
delegate(document.querySelector('table'), 'click', 'tr', function(e) {
  // this — нажатая строка таблицы
  console.log('Строка:', this.dataset.id)
})