Vue Front-end Инженер

Vue Front-end Инженер

Роадмап навыков для прокачки

Способы предотвращения DOM-событий

Browser APIОбъекты документа и страницыСобытия

Основная идея

Предотвращение DOM-событий — это механизмы, позволяющие контролировать поведение событий: отменять действия браузера по умолчанию, останавливать всплытие событий вверх по DOM-дереву или предотвращать срабатывание других обработчиков.

Ключевые аспекты

  • preventDefault() — отменяет действие браузера по умолчанию (переход по ссылке, отправка формы)
  • stopPropagation() — останавливает всплытие события к родительским элементам
  • stopImmediatePropagation() — останавливает всплытие и другие обработчики на текущем элементе
  • return false — в jQuery отменяет и действие, и всплытие; в чистом JS работает только в HTML-атрибутах

Типичные сценарии использования

  • Кастомная обработка формы без перезагрузки страницы
  • Создание собственного контекстного меню
  • Предотвращение перехода по ссылке для SPA
  • Изоляция обработки кликов в модальных окнах

Плюсы контроля над событиями

  • Полный контроль над поведением интерфейса
  • Возможность создавать кастомные UX-паттерны
  • Предотвращение нежелательных действий пользователя

Частые ошибки на собеседованиях

  • Путают preventDefault() и stopPropagation() — первый отменяет действие браузера, второй — всплытие
  • Думают, что return false работает одинаково везде — в addEventListener он ничего не делает
  • Не знают про stopImmediatePropagation() — останавливает даже обработчики на том же элементе
  • Злоупотребляют stopPropagation() — это может сломать аналитику и другие глобальные обработчики

Введение и проблематика

При работе с DOM-событиями часто нужно не только реагировать на них, но и контролировать их поведение. Браузер имеет стандартные действия для многих событий (переход по ссылке, отправка формы), а события "всплывают" вверх по DOM-дереву к родительским элементам.

JavaScript предоставляет методы для полного контроля над событиями: отмена действий по умолчанию, остановка всплытия, предотвращение срабатывания других обработчиков.

Зачем нужно предотвращать события?

  • Кастомная логика форм — отправка через AJAX без перезагрузки
  • SPA-навигация — клик по ссылке без перехода на другую страницу
  • Кастомные UI-компоненты — своё контекстное меню, drag-and-drop
  • Изоляция компонентов — клик внутри модалки не должен закрывать её

Базовая теория

Три уровня контроля

graph TB A[Контроль над событием] --> B[preventDefault] A --> C[stopPropagation] A --> D[stopImmediatePropagation] B --> B1[Отменяет действие браузера] C --> C1[Останавливает всплытие] D --> D1[Останавливает всё]
МетодЧто делает
preventDefault()Отменяет действие браузера по умолчанию
stopPropagation()Останавливает всплытие к родителям
stopImmediatePropagation()Останавливает всё: всплытие + другие обработчики

event.preventDefault()

Отменяет стандартное действие браузера для события. Само событие при этом продолжает всплывать.

Какие действия можно отменить?

СобытиеДействие по умолчанию
click на <a>Переход по ссылке
submit на <form>Отправка формы и перезагрузка
keydown в <input>Ввод символа
contextmenuПоказ контекстного меню
wheelПрокрутка страницы
dragstartНачало перетаскивания

Пример: кастомная отправка формы

js
const form = document.querySelector('form');
 
form.addEventListener('submit', async (e) => {
  // Отменяем стандартную отправку (перезагрузку страницы)
  e.preventDefault();
 
  const formData = new FormData(form);
 
  // Отправляем через fetch
  const response = await fetch('/api/submit', {
    method: 'POST',
    body: formData
  });
 
  if (response.ok) {
    showMessage('Форма отправлена!');
  }
});

Пример: SPA-навигация

js
// Перехватываем клики по ссылкам для SPA
document.addEventListener('click', (e) => {
  const link = e.target.closest('a');
 
  if (link && link.href.startsWith(window.location.origin)) {
    e.preventDefault(); // Отменяем переход
    router.navigate(link.href); // Используем SPA-роутер
  }
});

Пример: кастомное контекстное меню

js
document.addEventListener('contextmenu', (e) => {
  e.preventDefault(); // Отменяем стандартное меню браузера
 
  showCustomMenu(e.clientX, e.clientY);
});

Проверка: можно ли отменить событие?

js
document.addEventListener('scroll', (e) => {
  // Некоторые события нельзя отменить
  console.log(e.cancelable); // false для scroll
 
  if (e.cancelable) {
    e.preventDefault();
  }
});
⚠️

Не все события можно отменить. Свойство event.cancelable показывает, поддерживает ли событие отмену. Например, scroll отменить нельзя.


event.stopPropagation()

Останавливает всплытие события вверх по DOM-дереву. Обработчики на родительских элементах не сработают.

Как работает всплытие

html
<div id="outer">
  <div id="inner">
    <button id="button">Клик</button>
  </div>
</div>
js
document.getElementById('button').addEventListener('click', () => {
  console.log('1. Button');
});
 
document.getElementById('inner').addEventListener('click', () => {
  console.log('2. Inner');
});
 
document.getElementById('outer').addEventListener('click', () => {
  console.log('3. Outer');
});
 
// При клике на кнопку:
// 1. Button
// 2. Inner
// 3. Outer

Остановка всплытия

js
document.getElementById('button').addEventListener('click', (e) => {
  e.stopPropagation(); // Останавливаем всплытие
  console.log('1. Button');
});
 
document.getElementById('inner').addEventListener('click', () => {
  console.log('2. Inner'); // Не выполнится!
});
 
document.getElementById('outer').addEventListener('click', () => {
  console.log('3. Outer'); // Не выполнится!
});
 
// При клике на кнопку:
// 1. Button

Практический пример: модальное окно

html
<div class="modal-overlay">
  <div class="modal-content">
    <h2>Заголовок</h2>
    <p>Контент модалки</p>
  </div>
</div>
js
const overlay = document.querySelector('.modal-overlay');
const content = document.querySelector('.modal-content');
 
// Клик по оверлею закрывает модалку
overlay.addEventListener('click', () => {
  closeModal();
});
 
// Клик по контенту НЕ должен закрывать
content.addEventListener('click', (e) => {
  e.stopPropagation(); // Не даём клику дойти до overlay
});
🚫

Осторожно! Чрезмерное использование stopPropagation() может сломать аналитику, глобальные обработчики и другую функциональность, которая полагается на всплытие событий.


event.stopImmediatePropagation()

Останавливает всё: и всплытие, и другие обработчики на том же элементе.

Разница со stopPropagation

js
const button = document.querySelector('button');
 
button.addEventListener('click', (e) => {
  e.stopPropagation();
  console.log('Первый обработчик');
});
 
button.addEventListener('click', () => {
  console.log('Второй обработчик'); // Выполнится!
});
 
// При клике:
// Первый обработчик
// Второй обработчик

Когда использовать?

js
// Предотвращение выполнения обработчиков из сторонних библиотек
element.addEventListener('click', (e) => {
  if (someCondition) {
    e.stopImmediatePropagation();
    // Наша кастомная логика вместо библиотечной
    handleCustomClick();
  }
}, { capture: true }); // capture: true — выполнится первым

return false

В HTML-атрибутах (устаревший способ)

html
<!-- return false отменяет действие по умолчанию -->
<a href="https://example.com" onclick="return false;">Ссылка</a>
 
<!-- Также работает как preventDefault() -->
<form onsubmit="return false;">
  <button>Отправить</button>
</form>

В addEventListener

js
const link = document.querySelector('a');
 
link.addEventListener('click', () => {
  // return false ничего не делает в addEventListener!
  return false; // Не отменит переход по ссылке
});
 
// Нужно использовать preventDefault()
link.addEventListener('click', (e) => {
  e.preventDefault(); // Правильный способ
});
⚠️

return false работает только в HTML-атрибутах (onclick="return false") и в jQuery. В addEventListener он бесполезен — используйте preventDefault().

В jQuery

js
// В jQuery return false = preventDefault() + stopPropagation()
$('a').on('click', function() {
  return false; // Отменяет действие И всплытие
});
 
// Эквивалентно:
$('a').on('click', function(e) {
  e.preventDefault();
  e.stopPropagation();
});

Сравнение методов

МетодОтменяет действие браузераОстанавливает всплытиеОстанавливает другие обработчики
preventDefault()ДаНетНет
stopPropagation()НетДаНет
stopImmediatePropagation()НетДаДа
return false (HTML)ДаНетНет
return false (jQuery)ДаДаНет

Пограничные кейсы

passive и preventDefault несовместимы

js
// passive: true говорит браузеру, что preventDefault() не будет вызван
document.addEventListener('touchstart', (e) => {
  e.preventDefault(); // Ошибка в консоли! Игнорируется.
}, { passive: true });
 
// Правильно — без passive
document.addEventListener('touchstart', (e) => {
  e.preventDefault(); // Работает
}, { passive: false });
🚫

Если указана опция passive: true, вызов preventDefault() будет проигнорирован и выведет предупреждение в консоль.

Событие уже обработано?

js
element.addEventListener('click', (e) => {
  // Проверяем, не отменено ли уже действие
  if (e.defaultPrevented) {
    console.log('Кто-то уже вызвал preventDefault()');
    return;
  }
 
  e.preventDefault();
});

Фаза захвата vs всплытие

js
// stopPropagation работает в обе стороны
document.addEventListener('click', (e) => {
  e.stopPropagation();
  console.log('Захват на document — событие не дойдёт до элемента');
}, { capture: true });
 
button.addEventListener('click', () => {
  console.log('Этот обработчик не сработает');
});

Лучшие практики

Когда использовать preventDefault()

js
// ✅ Хорошо — кастомная логика вместо стандартной
form.addEventListener('submit', (e) => {
  e.preventDefault();
  submitViaAjax();
});
 
// ✅ Хорошо — SPA-навигация
link.addEventListener('click', (e) => {
  e.preventDefault();
  router.push(link.href);
});

Когда избегать stopPropagation()

js
// ❌ Плохо — может сломать аналитику
button.addEventListener('click', (e) => {
  e.stopPropagation(); // Google Analytics не зафиксирует клик
});
 
// ✅ Лучше — проверять target
document.addEventListener('click', (e) => {
  if (!e.target.closest('.modal-content')) {
    closeModal();
  }
});

Вопросы интервьюера

Q: В чём разница между preventDefault() и stopPropagation()?

preventDefault() отменяет действие браузера (переход по ссылке, отправка формы). stopPropagation() останавливает всплытие события к родительским элементам. Это независимые механизмы.

Q: Почему return false не работает в addEventListener?

return false работает только в HTML-атрибутах onclick и в jQuery. В addEventListener возвращаемое значение игнорируется — нужно явно вызывать preventDefault() и/или stopPropagation().

Q: Что делает stopImmediatePropagation()?

Останавливает и всплытие, и выполнение других обработчиков на том же элементе. stopPropagation() только останавливает всплытие, но другие обработчики на том же элементе выполнятся.

Q: Почему не стоит злоупотреблять stopPropagation()?

Это может сломать глобальные обработчики: аналитику (Google Analytics), закрытие модалок по клику вне них, горячие клавиши. Лучше проверять event.target вместо остановки всплытия.


Источники