Перейти к содержанию

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

Определение

Приём в JavaScript, когда один обработчик вешается на контейнер и ловит события от всех потомков через всплытие, определяя цель методом event.target.closest().

4 мин 36 Обновлено 12 августа

Делегирование событий (event delegation) – классический паттерн работы с DOM и один из стандартных вопросов на собеседованиях по JavaScript. Вместо того чтобы назначать обработчик каждой кнопке списка, один обработчик вешают на общего предка – и он ловит события всех потомков, включая элементы, которых на момент загрузки страницы ещё не существовало. Паттерн часто сочетают с preventDefault() (перехват кликов по ссылкам) и data-атрибутами (декларативное описание действий прямо в разметке).

Как это работает

Механизм держится на всплытии (event bubbling): событие срабатывает на самом вложенном элементе и поднимается по цепочке предков – от кнопки к её контейнеру, затем к body и document. Обработчик на контейнере получает события всех потомков, а где именно произошёл клик, показывает свойство event.target. У всплытия есть и обратная фаза – погружение (capturing): событие сначала спускается от document к цели. По умолчанию она выключена и включается опцией { capture: true } – через неё, кстати, можно делегировать даже невсплывающий focus. Типовой код с поиском цели через closest():

table.addEventListener('click', (event) => {
  const td = event.target.closest('td'); // клик мог прийти по вложенному тегу
  if (!td) return;                       // клик мимо ячеек
  if (!table.contains(td)) return;       // td из вложенной таблицы – отсекаем
  highlight(td);
});

Метод closest() здесь обязателен: если внутри ячейки лежит вложенный тег (например, <strong>), event.target укажет на него, а не на саму ячейку. Проверка table.contains(td) страхует от ловушки, о которой почти не пишут: closest() может найти подходящую под селектор ячейку вложенной таблицы или стороннего виджета, и обработчик сработает не на своём элементе.

event.target и event.currentTarget – в чём разница

event.target и event.currentTarget при всплытии
СвойствоЧто содержитПоведение при всплытии
event.targetЭлемент, на котором событие изначально произошлоНе меняется на всём пути всплытия
event.currentTargetЭлемент, на котором висит текущий обработчикМеняется на каждом уровне цепочки

В делегировании работают именно с event.target: currentTarget всегда укажет на контейнер, на котором висит обработчик, – по нему удобно разве что отличать «свой» контейнер, когда один обработчик обслуживает несколько блоков.

Обязательное условие паттерна – событие должно всплывать. Не все события это делают, и это первая причина «сломанного» делегирования:

Какие события всплывают и чем заменить остальные
СобытиеВсплываетЗамена для делегирования
click, input, keydownДаЗамена не нужна
focus / blurНетfocusin / focusout либо focus с capture: true
mouseenter / mouseleaveНетmouseover / mouseout

У mouseenter есть и вторая проблема: при глубокой вложенности он порождает лавину событий, поэтому MDN прямо рекомендует mouseover для таких случаев. Ещё одна ловушка – event.stopPropagation() во вложенном обработчике: событие не дойдёт до предка, и делегирование молча перестанет работать. Поэтому в низкоуровневых обработчиках внутри делегируемого контейнера stopPropagation() лучше не вызывать.

Зачем это нужно

Три измеримые выгоды паттерна:

  • Экономия памяти – один listener вместо сотен: для таблицы на тысячу ячеек или ленты товаров разница ощутимая.
  • Динамические элементы – обработчик на предке автоматически охватывает узлы, добавленные позже: кнопка «показать ещё», результаты живого поиска, виджеты, которые перерисовываются по таймеру setInterval().
  • Меньше кода – одна точка инициализации, проще поддержка и нет «забытых» обработчиков после перерисовки DOM.

Для владельца сайта всё это конвертируется в отзывчивый интерфейс на слабых устройствах и меньший объём кода, который нужно поддерживать и тестировать. А для разработчика тема имеет ещё одно измерение: делегирование входит в стандартный набор интервью-вопросов по JavaScript – его спрашивают, чтобы проверить понимание всплытия и работы с DOM в целом.

Цена невелика, но она есть: обработчик на предке срабатывает на каждое событие в контейнере, включая нецелевые, – это небольшая дополнительная нагрузка на CPU, обычно незаметная. Поэтому для двух-трёх статических кнопок делегирование избыточно: прямой addEventListener проще и читается лучше. Паттерн раскрывается на больших списках, таблицах и любом динамически генерируемом контенте.

Пример

Развитие паттерна – «поведение» через data-атрибуты: действие описывается прямо в разметке, а один обработчик на контейнере читает его из dataset и вызывает нужную функцию:

<div id="menu">
  <button data-action="save">Сохранить</button>
  <button data-action="load">Загрузить</button>
</div>

<script>
menu.addEventListener('click', (event) => {
  const action = event.target.dataset.action;
  if (action) actions[action]();
});
</script>

Так я собираю аккордеоны и табы в шаблонах Битрикс-компонентов: элементы списка выводятся циклом, их количество заранее неизвестно, а весь JavaScript – один делегированный обработчик, который по data-атрибуту находит панель и переключает ей aria-expanded. При догрузке элементов по AJAX перевешивать ничего не нужно: клики по новым кнопкам всплывают до того же контейнера и обрабатываются тем же кодом. Тот же подход масштабируется на корзину, фильтры каталога, выпадающие меню: действия описаны в разметке, а весь JavaScript – несколько десятков строк на любое число элементов.

Частые вопросы

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

Это приём, при котором один обработчик вешается на общего предка вместо множества обработчиков на каждом элементе. Событие всплывает от нажатого элемента вверх по DOM, обработчик ловит его на контейнере и по event.target определяет, где именно произошёл клик. Так делают меню, таблицы и списки с сотнями однотипных элементов.

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

Обработчик висит на предке, а не на самих элементах, поэтому новые узлы подхватываются автоматически. Кнопке, добавленной по AJAX или таймеру, не нужно ничего назначать: её клик всплывёт до контейнера и попадёт в тот же обработчик. Это главное преимущество паттерна для лент, живого поиска и подгрузки «показать ещё».

Чем event.target отличается от event.currentTarget?

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

Зачем в делегировании нужен метод closest()?

Клик может прийти по вложенному тегу: если внутри кнопки лежит span или иконка, event.target укажет на них, а не на кнопку. event.target.closest(селектор) поднимается по предкам и находит нужный элемент. Дополнительно стоит проверять container.contains(найденное), чтобы отсечь совпадения из вложенных виджетов.

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

Те, что не всплывают: focus и blur заменяют на focusin и focusout (либо вешают focus с capture: true), mouseenter и mouseleave – на mouseover и mouseout. Остальные популярные события (click, input, keydown) всплывают и делегируются без замен.

Делегирование ухудшает производительность?

Наоборот: один обработчик вместо сотен экономит память, особенно на больших таблицах и списках. Цена в другом – обработчик на предке срабатывает на каждое событие в контейнере, даже нецелевое, что даёт небольшую нагрузку на CPU. На практике она обычно незаметна; а для пары статических кнопок паттерн просто не нужен.

Почему делегирование не работает?

Три частые причины. Событие не всплывает (focus, mouseenter) – нужна всплывающая замена вроде focusin или mouseover. Вложенный обработчик вызвал event.stopPropagation() – событие не дошло до предка. Клик пришёл по вложенному тегу, а код сравнивает event.target напрямую вместо event.target.closest().

Связанные термины

Материалы по теме

Что почитать дальше по этой теме

Статья Что боты находят на сайте, а вы – нет Боты ходят по изнанке сайта, куда живой человек не забредёт, и фиксируют в логах то, чего не видно ни в Метрике, ни на витрине: падающие с ошибкой 500 страницы каталога, битый файл с 747 обращениями, поток сканеров секретов. Разбираю на реальных логах, что искать и как чинить. 7 мин 216 22 июля 2026 Инструкция Как выгрузить и читать access-логи, если хостинг хранит их три дня Access-лог – единственное место, где виден весь трафик сайта, включая ИИ-ботов, которых не замечают счётчики. Разбираю на практике: где взять логи, как не потерять их из-за ротации, какие команды показывают ботов, ошибки и подозрительную активность. 7 мин 170 21 июля 2026 Флагманский гайд AI-анализ email-обращений: методика на стыке Яндекс.Метрики и Claude Code Классический email-трекинг отвечает на вопрос «откуда пришло обращение». AI-слой отвечает на вопросы, которые раньше требовали ручного аналитика: значим ли email на фоне звонков, форм и мессенджеров, почему один источник даёт качественные обращения, а другой – пустые, какие сегменты визитов предшествуют письму, что написать в ответ с учётом пути пользователя. Здесь – как я собираю это на уже имеющемся стеке: Logs API Метрики, PostgreSQL и Claude Code, без новых платных сервисов. 11 мин 297 26 мая 2026 Флагманский гайд Майкор: ИИ-аудит проекта по 4 точкам контакта Майкор – это перекрёстный ИИ-аудит проекта в 4 точках контакта: сайт, контекстная реклама, AI-поиск и Яндекс.Карты. Я анализирую каждую систему и смотрю связки между ними – где маркетинг рассказывает одно, реклама ведёт на другое, а AI-системы цитируют третий номер телефона. В одном из моих аудитов медицинской клиники в Москве у организации в индексе AI-сервисов оказалось 6 разных номеров и всего 7 упоминаний на 64 проверочных запроса – при том, что в обычной выдаче Яндекса клиника была в топ-1. Майкор закрывает такие разрывы за один аудит вместо четырёх раздельных. 16 мин 501 13 мая 2026 Гайд FAQPage Schema: как разметить блок частых вопросов и попасть в AI-ответы FAQPage schema – один из самых эффективных типов разметки для попадания в AI-ответы. Страницы с FAQPage schema цитируются AI-поисковиками в 2,7 раза чаще. В этом гайде – формат разметки, правила написания ответов для AI, реализация на 1С-Битрикс и типичные ошибки, которые обнуляют эффект. 8 мин 331 25 апреля 2026