Распространение событий и делегирование
Free PreviewКогда происходит событие — например, клик — браузер не просто вызывает обработчик на целевом элементе. Событие проходит путь по всему DOM-дереву, и это важно понимать.
Всплытие событий
По умолчанию события всплывают (bubble) — то есть после срабатывания на целевом элементе событие поднимается вверх по дереву, поочерёдно вызывая обработчики на всех родительских элементах, вплоть до document.
Возьмём такую структуру:
<div class="outer"> <div class="inner"> <button>Нажми меня</button> </div> </div>html
document.querySelector('.outer').addEventListener('click', () => console.log('outer')); document.querySelector('.inner').addEventListener('click', () => console.log('inner')); document.querySelector('button').addEventListener('click', () => console.log('button'));js
При клике на кнопку в консоли появится:
button
inner
outer
Событие началось на button, потом поднялось к .inner, потом к .outer. Это и есть всплытие.
На практике это часто работает как ожидается и не мешает. Но иногда всплытие нужно остановить.
stopPropagation
Метод e.stopPropagation() останавливает дальнейшее распространение события — оно не поднимется к родительским элементам:
document.querySelector('button').addEventListener('click', (e) => { e.stopPropagation(); // дальше не всплывёт console.log('button'); });js
Теперь при клике на кнопку сработает только её обработчик, и событие дальше не пойдёт.
Используйте stopPropagation осторожно: если им злоупотреблять, код становится сложно отлаживать — некоторые обработчики перестают работать без очевидной причины.
Фаза погружения
На самом деле событие проходит три фазы:
- Погружение (capturing) — событие идёт сверху вниз: от
documentк целевому элементу - Целевая фаза — событие на самом элементе
- Всплытие (bubbling) — событие идёт снизу вверх обратно к
document
По умолчанию addEventListener подписывается на фазу всплытия. Чтобы поймать событие на фазе погружения, передайте третий аргумент { capture: true }:
document.querySelector('.outer').addEventListener('click', () => { console.log('outer — погружение'); }, { capture: true });js
На практике погружение используется редко — всплытие покрывает почти все задачи.
target и currentTarget
Важно понимать разницу между двумя свойствами объекта события:
e.target— элемент, на котором событие произошло (где кликнули)e.currentTarget— элемент, на котором висит обработчик
document.querySelector('.outer').addEventListener('click', (e) => { console.log(e.target); // элемент на котором кликнули (например, button) console.log(e.currentTarget); // всегда .outer — здесь висит обработчик });js
Эта разница становится особенно полезной в следующем паттерне.
Делегирование событий
Вместо того чтобы вешать обработчик на каждый дочерний элемент, можно повесить один обработчик на родителя и проверять e.target — чтобы понять, на каком именно дочернем элементе произошло событие.
Это называется делегирование событий. Представьте список из множества карточек:
<ul class="attractions"> <li data-id="hermitage">Эрмитаж</li> <li data-id="peterhof">Петергоф</li> <li data-id="nevsky">Невский проспект</li> </ul>html
Вместо трёх обработчиков — один:
document.querySelector('.attractions').addEventListener('click', (e) => { const item = e.target.closest('li'); // ближайший li вверх по дереву if (!item) return; // кликнули мимо li console.log(item.dataset.id); // 'hermitage', 'peterhof' и т.д. });js
Метод closest(selector) поднимается вверх по DOM-дереву и возвращает ближайший родительский элемент, соответствующий селектору — или сам элемент, если он соответствует. Это удобно, когда внутри li есть вложенные элементы.
Делегирование полезно по двум причинам:
- Меньше обработчиков — один вместо N, это немного эффективнее
- Работает для динамически добавленных элементов — если вы добавите новый
liв список через JS, обработчик на нём уже будет работать, потому что он висит на родителе
В следующем уроке применим знания об событиях на практике — создадим карусель с изображениями на нашей странице о Санкт-Петербурге.