
Когда внутри кнопки появляется иконка или 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.




