W3docs

JavaScript Despacho de Eventos Personalizados

Aprende a crear y despachar eventos personalizados en JavaScript con CustomEvent y dispatchEvent, pasar datos mediante detail y desacoplar componentes.

Despacho de eventos personalizados en JavaScript

El navegador dispara eventos integrados como click, submit y keydown de forma automática. Pero JavaScript también te permite crear y disparar tus propios eventos en cualquier nodo del DOM, y escucharlos con el mismo addEventListener() que ya usas para los eventos nativos.

Esta es la base del diseño de aplicaciones con acoplamiento débil: una parte de tu código anuncia que algo ocurrió ("datos cargados", "carrito actualizado") sin saber ni importarle quién está escuchando. Esta página explica cómo crear eventos personalizados con el constructor CustomEvent, adjuntarles datos, despacharlos con dispatchEvent() y manejar los errores comunes.

Creación de un evento personalizado

Usa el constructor CustomEvent. El primer argumento es el tipo del evento (el nombre que usarás después para escucharlo); el segundo es un objeto de opciones:

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

Las tres opciones que usarás con más frecuencia son:

  • detail — cualquier valor (object, string, número) que quieras adjuntar al evento. El listener lo recupera como event.detail. Este es el canal dedicado para datos personalizados; las propiedades de los eventos nativos son de solo lectura.
  • bubbles — cuando es true, el evento sube por los elementos ancestros tras dispararse, por lo que un listener en un padre (o en document) puede capturarlo. El valor predeterminado es false.
  • cancelable — cuando es true, un listener puede llamar a event.preventDefault() para indicar que la acción predeterminada debe omitirse. El valor predeterminado es false.

CustomEvent vs. Event

También existe un constructor Event simple, pero no puede transportar datos — no tiene detail. Utiliza siempre CustomEvent cuando necesites pasar información junto con el evento:

// 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() }
});

Despacho del evento

Un evento creado no hace nada hasta que lo disparas en un elemento con dispatchEvent():

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

Hay dos cosas importantes sobre dispatchEvent():

  1. Se ejecuta de forma síncrona — a diferencia de setTimeout, los listeners se ejecutan de inmediato, antes de que se ejecute la línea siguiente a dispatchEvent().
  2. Devuelve un boolean: false si el evento era cancelable y un listener llamó a preventDefault(), y true en caso contrario. Esto permite que el código que despachó el evento reaccione ante un "veto":
let cancelled = !target.dispatchEvent(event);
if (cancelled) {
  console.log("A listener prevented the default action.");
}

Puedes despachar en cualquier elemento. Despachar en document funciona porque los eventos con bubbling lo alcanzan, pero disparar en el elemento específico que "posee" el evento (y dejar que suba) mantiene tus listeners acotados y evita manejadores globales accidentales.

Escucha de un evento personalizado

No hay ninguna API especial en el lado del listener — addEventListener() funciona exactamente igual que con click. Los datos llegan en event.detail:

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

Ejemplos prácticos

Ejemplo 1: Comunicación entre componentes

Supón que dos partes independientes de una página necesitan comunicarse sin tener referencias directas entre sí. Una despacha un evento personalizado; la otra escucha. Como el evento tiene bubbles, el listener puede estar en 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);
});

Cómo funciona:

  • Al hacer clic en el botón, se crea y despacha un evento componentMessage que lleva un mensaje en detail.
  • Un listener en otro lugar captura el evento y reacciona. Ninguno de los dos lados importa al otro — el nombre del evento es el único contrato.

Ejemplo 2: Actualización de la interfaz tras un cambio de datos

Los eventos personalizados te permiten separar la lógica de datos de la lógica de renderizado. La capa de datos anuncia dataUpdated; la capa de interfaz escucha y vuelve a renderizar. Aquí la actualización se dispara tras una breve espera para simular una petición asíncrona:

<!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>

Cómo funciona:

  • Tras dos segundos, updateData() despacha un evento dataUpdated con los nuevos datos en detail.
  • El listener de la interfaz lee event.detail.data y actualiza la página. La función que produjo los datos nunca toca el DOM directamente.

Cancelación de un evento personalizado

Cuando un evento es cancelable, un listener puede llamar a preventDefault(). El despachador entonces recibe un valor de retorno false de dispatchEvent() y puede omitir su comportamiento predeterminado — el mismo patrón que usa el navegador para el envío de formularios o los clics en enlaces. Este fragmento se ejecuta en Node y demuestra el flujo síncrono:

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 es la interfaz base de la que heredan los nodos del DOM, por lo que la misma lógica se aplica tanto si despachas en un elemento del navegador como en un EventTarget independiente.

Conclusión

Los eventos personalizados te ofrecen una forma limpia y sin dependencia de frameworks para conectar las partes de una aplicación. Créalos con CustomEvent, adjunta un payload mediante detail, dispáralos con dispatchEvent() y escúchalos con el familiar addEventListener(). Establece bubbles: true cuando un padre deba capturar el evento, y cancelable: true cuando el despachador necesite respetar el veto de un listener. El resultado es código donde los módulos se comunican a través de eventos con nombre en lugar de referencias directas — más fácil de probar, ampliar y mantener.

Para profundizar, consulta cómo viajan los eventos por el DOM en bubbling y captura, cómo se adjuntan los listeners en manejo de eventos en el DOM y cómo preventDefault() afecta a las acciones predeterminadas del navegador.

Práctica

Práctica
¿Cuáles de las siguientes afirmaciones sobre los eventos personalizados en JavaScript son correctas?
¿Cuáles de las siguientes afirmaciones sobre los eventos personalizados en JavaScript son correctas?
Was this page helpful?