Что такое паттерн делегирования событий в JavaScript
Что проверяют
Понимаете ли вы всплытие и применяете ли делегирование осознанно, а не как заученный приём. Это заодно проверка на то, работали ли вы с динамическими списками: там разница между сотней обработчиков и одним видна и в памяти, и в коде.
Как отвечать
Обработчик вешается на общего предка, а внутри определяется, из-за какого потомка событие пришло. Работает это благодаря всплытию: событие поднимается от цели вверх по дереву, и предок видит его в event.target.
list.addEventListener('click', (e) => {
const btn = e.target.closest('[data-action="remove"]');
if (!btn || !list.contains(btn)) return;
remove(btn.dataset.id);
});Что даёт: один обработчик вместо множества, новые элементы работают сразу после вставки без переподписки, отписка тоже одна, а логика собрана в одном месте.
Обязательно назовите границы. События, которые не всплывают, так не ловятся: у focus и blur для этого есть всплывающие focusin и focusout, а вместо mouseenter и mouseleave берут mouseover и mouseout с проверкой relatedTarget. И отдельно про React: синтетические события уже слушаются на корне контейнера, так что внутри React-дерева своё делегирование обычно лишнее.
Где спотыкаются
Проверяют цель через event.target.classList.contains и ломаются на вложенной вёрстке: клик попадает в иконку или текст внутри кнопки, условие не срабатывает. Правильный инструмент это closest, он поднимается от цели до нужного предка.
Забывают, что stopPropagation в потомке или внутри чужого виджета убивает делегирование. Симптом выглядит мистически: обработчик срабатывает везде, кроме одного участка списка.
Вешают всё подряд на document. Обработчик начинает вызываться на каждый клик страницы, а внутри у него тяжёлые проверки; на прокрутке и касаниях это вдобавок мешает браузеру, если слушатель не помечен пассивным. Точка подписки должна быть ближайшим общим контейнером, а не корнем документа.