← Назад к списку
ПрограммированиеFrontendJunior

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

Короткий ответ

  • Один обработчик на контейнер вместо тысячи на элементах
  • Работает благодаря всплытию (bubbling) событий
  • Цель определяется через event.target и closest
  • closest нужен из-за вложенной разметки внутри элементов
  • Новые элементы списка работают без дополнительного кода
  • Экономит память и время навешивания обработчиков
  • Не все события всплывают: focus, blur — нет

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

Как сказать вслух

пример ответа

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

Подробный ответ

Основной ответ

Событие проходит фазу погружения от корня к цели и фазу всплытия обратно; addEventListener по умолчанию слушает всплытие. При делегировании обработчик стоит на общем предке, а конкретная цель вычисляется так: event.target — самый глубокий элемент, по которому кликнули; target.closest('li.item') поднимается до ближайшего подходящего предка. Обязательная проверка — что closest вернул не null и что найденный элемент лежит внутри контейнера (currentTarget.contains). Данные элемента удобно хранить в data-атрибутах (dataset.id) и по ним выполнять действие. Выгоды: один listener вместо тысячи (память, скорость инициализации), автоматическая поддержка динамических элементов. Ограничения: часть событий не всплывает (focus/blur — используют focusin/focusout, mouseenter — mouseover), а stopPropagation во вложенном коде может «съесть» событие до контейнера.

Ключевые моменты

  • target против currentTarget. target — где произошло событие, currentTarget — где висит обработчик; путают постоянно.
  • closest для вложенности. Клик приходит в span внутри li; closest надёжно находит сам элемент списка.
  • Динамические элементы. Делегирование покрывает элементы, добавленные после навешивания обработчика, — главный практический плюс.
  • Невсплывающие события. focus и blur не всплывают; для делегирования берут focusin/focusout.

Практический контекст

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

Пример кода

const list = document.querySelector('#list');
list.addEventListener('click', (event) => {
  const item = event.target.closest('li[data-id]');
  if (!item || !list.contains(item)) return;
  openCard(item.dataset.id);
});
// Работает для всех <li data-id="...">, включая добавленные позже

Частые ошибки

  • Проверяют event.target === li строгим равенством и ломаются на вложенной разметке
  • Путают target и currentTarget
  • Не знают, что focus и blur не всплывают, и делегирование с ними «не работает»

ИП Кочкин Алексей Сергеевич · ИНН 390509026279 · ОГРНИП 325390000030973 · jiniys2005@yandex.ru