W3docs

Генерация пользовательских событий в JavaScript

Как создавать и генерировать пользовательские события в JavaScript с помощью CustomEvent и dispatchEvent, передавать данные через detail и разделять компоненты.

Генерация пользовательских событий в JavaScript

Браузер автоматически генерирует встроенные события, такие как click, submit и keydown. Однако JavaScript также позволяет создавать и генерировать собственные события на любом узле DOM, а затем прослушивать их с помощью того же addEventListener(), который используется для нативных событий.

Это основа слабосвязанной архитектуры приложений: одна часть кода сообщает, что что-то произошло («данные загружены», «корзина обновлена»), не зная и не заботясь о том, кто это слушает. На этой странице рассказывается, как создавать пользовательские события с помощью конструктора CustomEvent, прикреплять к ним данные, генерировать их с помощью dispatchEvent() и избегать распространённых ошибок.

Создание пользовательского события

Используйте конструктор CustomEvent. Первый аргумент — это тип события (имя, по которому вы будете его прослушивать); второй — объект настроек:

let event = new CustomEvent("myEvent", {
  detail: { message: "This is a custom event!" },
  bubbles: true,
  cancelable: true
});

Три параметра, которые используются чаще всего:

  • detail — любое значение (object, string, число), которое вы хотите прикрепить к событию. Слушатель читает его как event.detail. Это специальный канал для пользовательских данных; нативные свойства событий доступны только для чтения.
  • bubbles — при значении true событие всплывает через родительские элементы после срабатывания, поэтому слушатель на родителе (или document) может его поймать. По умолчанию false.
  • cancelable — при значении true слушатель может вызвать event.preventDefault(), чтобы сигнализировать о пропуске действия по умолчанию. По умолчанию false.

CustomEvent и Event

Существует также простой конструктор Event, но он не может передавать данные — у него нет detail. Всегда используйте CustomEvent, когда нужно передать информацию вместе с событием:

// No way to attach data here:
let bare = new Event("ping", { bubbles: true });

// Use CustomEvent to send a payload:
let withData = new CustomEvent("ping", {
  bubbles: true,
  detail: { at: Date.now() }
});

Генерация события

Созданное событие ничего не делает, пока вы не запустите его на элементе с помощью dispatchEvent():

let target = document.getElementById("box");
target.dispatchEvent(event);

Два важных момента о dispatchEvent():

  1. Он выполняется синхронно — в отличие от setTimeout, слушатели выполняются немедленно, до строки, следующей после dispatchEvent().
  2. Он возвращает boolean: false, если событие было cancelable и слушатель вызвал preventDefault(), в противном случае true. Это позволяет коду, сгенерировавшему событие, реагировать на «вето»:
let cancelled = !target.dispatchEvent(event);
if (cancelled) {
  console.log("A listener prevented the default action.");
}

Вы можете генерировать событие на любом элементе. Генерация на document работает, потому что всплывающие события достигают его, но запуск на конкретном элементе, которому «принадлежит» событие (с последующим всплытием), позволяет держать слушатели в нужной области и избегать случайных глобальных обработчиков.

Прослушивание пользовательского события

На стороне прослушивания нет специального API — addEventListener() работает точно так же, как и для click. Данные поступают через event.detail:

element.addEventListener("myEvent", function (event) {
  console.log(event.detail.message); // "This is a custom event!"
});

Практические примеры

Пример 1: Взаимодействие между компонентами

Предположим, что две независимые части страницы должны взаимодействовать без прямых ссылок друг на друга. Одна генерирует пользовательское событие, другая слушает. Поскольку событие bubbles, слушатель может находиться на document:

<button id="sender">Send Message</button>
// Listener in another component
document.addEventListener('componentMessage', function(event) {
  alert('Received message: ' + event.detail.message);
});

document.getElementById('sender').addEventListener('click', function() {
  // Create and dispatch the custom event
  let customEvent = new CustomEvent('componentMessage', {
    detail: { message: 'Hello from another component!' },
    bubbles: true,
    cancelable: true
  });
  document.dispatchEvent(customEvent);
});

Как это работает:

  • Нажатие кнопки создаёт и генерирует событие componentMessage, несущее сообщение в detail.
  • Слушатель в другом месте перехватывает событие и реагирует на него. Ни одна из сторон не импортирует другую — имя события является единственным контрактом.

Пример 2: Обновление UI после изменения данных

Пользовательские события позволяют разделить логику данных и логику отображения. Уровень данных объявляет dataUpdated; уровень UI слушает и перерисовывается. Здесь обновление запускается после небольшой задержки, имитирующей асинхронный запрос:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Custom Event UI Update Example</title>
</head>
<body>
<h1>User Status</h1>
<div id="userInfo">Loading user information...</div>

<script>
  // Function to simulate a data update
  function updateData() {
    let dataUpdateEvent = new CustomEvent('dataUpdated', {
      detail: { data: { username: 'user123', status: 'active' } }
    });
    document.dispatchEvent(dataUpdateEvent);
  }

  // UI component listening for data updates
  document.addEventListener('dataUpdated', function(event) {
    let userData = event.detail.data;
    document.getElementById('userInfo').innerHTML =
      `Username: <strong>${userData.username}</strong>, Status: <strong>${userData.status}</strong>`;
  });

  // Trigger the update after 2 seconds
  setTimeout(updateData, 2000);
</script>
</body>
</html>

Как это работает:

  • Через две секунды updateData() генерирует событие dataUpdated с новыми данными в detail.
  • Слушатель UI читает event.detail.data и обновляет страницу. Функция, сформировавшая данные, никогда не обращается к DOM напрямую.

Отмена пользовательского события

Когда событие cancelable, слушатель может вызвать preventDefault(). Тогда диспетчер получает возвращаемое значение false от dispatchEvent() и может пропустить своё поведение по умолчанию — тот же паттерн, который браузер использует для отправки форм или переходов по ссылкам. Этот фрагмент выполняется в Node и демонстрирует синхронный поток:

let target = new EventTarget();

target.addEventListener("save", (event) => {
  // Veto the save
  event.preventDefault();
});

let event = new CustomEvent("save", { cancelable: true });
let notCancelled = target.dispatchEvent(event);

console.log(notCancelled);          // false
console.log(event.defaultPrevented); // true

EventTarget — это базовый интерфейс, от которого наследуются узлы DOM, поэтому та же логика применяется независимо от того, генерируете ли вы событие на элементе в браузере или на отдельном EventTarget.

Заключение

Пользовательские события дают чистый, независимый от фреймворков способ связывания частей приложения. Создавайте их с помощью CustomEvent, прикрепляйте данные через detail, запускайте с помощью dispatchEvent() и прослушивайте с помощью привычного addEventListener(). Устанавливайте bubbles: true, когда родитель должен перехватывать событие, и cancelable: true, когда диспетчеру нужно учитывать вето слушателя. В результате вы получаете код, где модули взаимодействуют через именованные события, а не через прямые ссылки — его легче тестировать, расширять и поддерживать.

Для углублённого изучения смотрите, как события проходят через DOM, в разделе всплытие и захват, как прикрепляются слушатели в обработке событий в DOM, и как preventDefault() влияет на действия браузера по умолчанию.

Практика

Практика
Какие утверждения о пользовательских событиях в JavaScript верны?
Какие утверждения о пользовательских событиях в JavaScript верны?
Was this page helpful?