Різниця між event.preventdefault() та event.stoppropagation()
preventDefault проти stopPropagation - один скасовує вбудовану реакцію браузера на подію, інший зупиняє спливання (bubbling) події до батьківських елементів DOM. Це два незалежних механізми, і виклик одного не впливає на інший.
Теорія
TL;DR
preventDefault()блокує те, що браузер робить після події: переходить за URL, відправляє форму, показує контекстне менюstopPropagation()блокує, куди подія рухається далі: вгору через батьківські елементи- Аналогія:
preventDefault= ігнорування дзвінка у двері (дзвінок був, але ніхто не відкриває);stopPropagation= звукоізоляція стін (сусіди взагалі не чують) stopPropagationсам по собі не зупинить відправку форми. Для цього потрібенpreventDefault.- Правило вибору: небажана поведінка браузера →
preventDefault; небажаний ланцюжок обробників →stopPropagation
Швидкий приклад
document.getElementById('parent').addEventListener('click', () => {
console.log('Батько спрацював');
});
document.getElementById('child').addEventListener('click', (e) => {
e.stopPropagation(); // Обробник батька не запуститься
console.log('Дочірній елемент спрацював');
});
// З stopPropagation: тільки "Дочірній елемент спрацював"
// Без нього: "Дочірній елемент спрацював", потім "Батько спрацював"Батьківський обробник мовчить завдяки stopPropagation. Виклик preventDefault тут нічого б не змінив, бо кнопка всередині <div> не має жодної вбудованої дії браузера.
Головна різниця
preventDefault() каже браузеру пропустити вбудовану відповідь: не переходити за посиланням, не перезавантажувати сторінку при відправці форми. Після цього виклику подія продовжує нормально подорожувати деревом DOM. stopPropagation() обрізає маршрут події, тому батьківські обробники не спрацьовують, але вбудована дія браузера виконується, якщо тільки ти також не викликав preventDefault(). Це два окремих прапорці на одному об'єкті події.
Коли використовувати
- Тег
<a>із власним роутингом замість стандартного переходу →preventDefault - Форма, що відправляє дані через
fetchзамість перезавантаження сторінки →preventDefault - Кнопка всередині картки, де картка теж має обробник кліку →
stopPropagationна кнопці - Модальне вікно з фоновим закриттям: клік всередині не повинен його закривати →
stopPropagationна контенті модалки - Кастомний дропдаун поверх посилання → обидва, спочатку
preventDefault
Таблиця порівняння
| Аспект | preventDefault() | stopPropagation() |
|---|---|---|
| Впливає на | Вбудовану дію браузера | Рух події через DOM |
| Обхід DOM | Не змінюється, батьки спрацьовують | Заблокований, батьки пропускаються |
| Встановлює прапорець | event.defaultPrevented = true | event.cancelBubble = true |
| Типове використання | <a> без навігації, форма без перезавантаження | Фон модалки, вкладені обробники |
| Вплив на інший метод | Відсутній | Відсутній |
Як браузер це обробляє
Коли подія виникає, вона проходить три фази: захоплення (від кореня до цільового елементу), target (на самому елементі), спливання (назад до кореня). preventDefault встановлює прапорець, який браузер перевіряє після завершення всього dispatch-циклу, перед виконанням вбудованої дії. stopPropagation виходить з циклу обробки раніше, тому обробники фази спливання більше не запускаються. Важливий нюанс: якщо батьківський слухач зареєстровано з { capture: true }, він запускається ще до обробника дочірнього елементу. Виклик stopPropagation в дочірньому елементі не скасує вже виконане.
Типові помилки
Помилка 1: очікування, що stopPropagation заблокує відправку форми
// Неправильно: форма все одно відправиться
submitButton.addEventListener('click', (e) => {
e.stopPropagation();
});
// Правильно
submitButton.addEventListener('click', (e) => {
e.preventDefault();
});stopPropagation керує маршрутом події, а не поведінкою браузера.
Помилка 2: кілька обробників на одному елементі
elem.addEventListener('click', fn1);
elem.addEventListener('click', fn2); // fn2 все одно запуститься, навіть якщо fn1 викликала stopPropagationstopPropagation зупиняє рух події до інших елементів. Для блокування обробників на тому ж елементі використовуй stopImmediatePropagation().
Помилка 3: модалка без stopPropagation на контенті
// Баг: будь-який клік всередині модалки спливає до overlay і викликає closeModal
overlay.addEventListener('click', closeModal);
// Виправлення
modalContent.addEventListener('click', (e) => e.stopPropagation());Де зустрічається в реальних проектах
- React:
e.stopPropagation()вonClickдля дропдаунів і вкладених компонентів; Vue має скорочення@click.stopі@click.prevent - Обробники форм у SPA-застосунках:
e.preventDefault()перед викликомfetch - jQuery:
.preventDefault()і.stopPropagation()як окремі методи-обгортки - Shadow DOM:
stopPropagationне перетинає межі shadow tree за замовчуванням
Питання на співбесіді
Q: Яка різниця між stopPropagation і stopImmediatePropagation?
A: stopPropagation зупиняє подію від потрапляння до батьківських елементів, але інші обробники на тому ж елементі в тій же фазі продовжують виконуватись. stopImmediatePropagation блокує обидва: і поточний елемент, і батьківські.
Q: Чи працює preventDefault у фазі захоплення?
A: Так. preventDefault не залежить від фази. Браузер перевіряє прапорець після завершення всього dispatch-циклу, тому виклик у будь-якій фазі дає однаковий результат.
Q: Чи можна викликати preventDefault на кастомній події?
A: Так, якщо подію створено з { cancelable: true }. Без цього параметру preventDefault не матиме ефекту, а event.defaultPrevented залишиться false.
Q: У React e.stopPropagation() зупиняє нативне DOM-спливання?
A: Ні. React використовує синтетичні події зі своїм механізмом делегування. e.stopPropagation() зупиняє спливання лише в межах React-дерева. Для блокування нативного DOM використовуй e.nativeEvent.stopImmediatePropagation().
Q: У Shadow DOM stopPropagation перетинає межу тіньового дерева?
A: Ні. Події перенацілюються на межах shadow DOM і залишаються в межах свого дерева. Щоб навмисно пробити межу, використовуй CustomEvent з параметром { composed: true }.
Приклади
Блокування переходу за посиланням
<a href="https://example.com" id="link">Перейти</a>document.getElementById('link').addEventListener('click', (e) => {
e.preventDefault();
console.log('Клік зафіксовано, навігацію заблоковано');
// Сторінка не змінюється, запускається власна логіка
});Подія виникла і спливла деревом DOM нормально. Скасовано лише вбудовану відповідь браузера. Батьківські обробники кліку, якщо такі є, отримали б цю подію без проблем.
Модальне вікно в React із stopPropagation
function Modal({ onClose }) {
return (
<div className="overlay" onClick={onClose}>
<div className="content" onClick={(e) => e.stopPropagation()}>
<p>Клік тут не закриє модалку</p>
<button onClick={onClose}>Закрити</button>
</div>
</div>
);
}
// Клік по .content: stopPropagation не дає спливти до .overlay, onClose не викликається
// Прямий клік по .overlay: onClose спрацьовуєЦе один з найпоширеніших патернів для модальних компонентів, і пропущений stopPropagation на контенті є класичним багом, який я виловлював у продакшені. Клік спливає до overlay і закриває модалку раніше, ніж користувач встигає зрозуміти що сталось.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.