Обработчики событий: когда всплытие помогает, а когда мешает

Схема вложенных DOM-элементов и направления события на экране

Когда внутри кнопки появляется иконка или span, обработчик с предположением «нажали ровно кнопку» может перестать работать. Всплытие позволяет слушать общий контейнер, а затем определить нужное действие. Но глобально останавливать распространение каждого события — плохая замена понятной структуре.

Пример с вложенным элементом кнопки

index.html

<!doctype html>
<html lang="ru"><meta charset="utf-8"><title>Делегирование</title>
<ul id="tasks"><li><button type="button" data-id="a"><span>Задача А</span></button></li>
<li><button type="button" data-id="b">Задача Б</button></li></ul>
<p id="log" aria-live="polite"></p>
<script>
const tasks = document.querySelector('#tasks');
tasks.addEventListener('click', event => {
  if (!(event.target instanceof Element)) return;
  const button = event.target.closest('button[data-id]');
  if (!button || !tasks.contains(button)) return;
  document.querySelector('#log').textContent = `Выбрано: ${button.dataset.id}`;
});
</script></html>

Нажатие на текст внутри span должно дать тот же результат, что и на свободную область кнопки. event.target описывает исходный элемент, а currentTarget — контейнер с обработчиком. MDN объясняет это различие в разделе всплытия. closest поднимается к подходящей кнопке; проверка принадлежности контейнеру ограничивает область действия.

Механизмы решают разные задачи
Что сравнить На что смотреть Что сделать
Делегирование Обработка действий потомков через контейнер Подходит для добавляемых кнопок списка
stopPropagation Остановка дальнейшего распространения Использовать только при конкретной необходимости
preventDefault Отмена стандартного действия, если допустимо Не путать с остановкой всплытия
currentTarget Элемент текущего обработчика Не считать его исходной кнопкой

Где возникают неожиданные связи

Если карточка реагирует на любой click, а внутри есть отдельная кнопка, одно действие может запускать два сценария. Лучше сначала уточнить разметку и зоны взаимодействия. Не помещайте интерактивные элементы в недопустимые вложения ради общего обработчика. Остановка всплытия может скрыть проблему и одновременно сломать обработку на другом уровне.

  • Нажать на span и на кнопку вне текста.
  • Нажать на пустую область списка: действие не должно выбираться.
  • Добавить новую кнопку в tasks и проверить без отдельного обработчика.
  • Активировать кнопки клавиатурой.

Этот пример работает с click, который распространяется подходящим образом. Нельзя переносить вывод на все события без проверки их свойств. Для сложного компонента сначала определите контракт событий, затем выберите фазу и место обработчика. Дополнительная остановка нужна по смыслу, а не для того, чтобы код случайно заработал.

Вопросы и ответы

Почему нужен instanceof Element?

Чтобы обращаться к closest только у подходящего DOM-объекта. Проверка явно описывает ожидаемый тип цели.

Нужен ли stopPropagation в примере?

Нет. Обработчик контейнера уже ограничивает нужное действие и не обязан мешать другим слушателям.

preventDefault остановит всплытие?

Нет. Это разные механизмы с разными задачами.

Делегирование заменяет семантику кнопок?

Нет. Настоящие button сохраняют привычную клавиатурную активацию, которой у произвольного кликабельного блока может не быть.

Источники: developer.mozilla.org.

Похожие записи