15/12/2019
En el mundo del desarrollo web con JavaScript, estamos acostumbrados a trabajar con eventos. El clic de un ratón, el envío de un formulario o la carga completa de una página son señales que el navegador nos envía para que podamos reaccionar y ejecutar nuestro código. Sin embargo, a medida que nuestras aplicaciones crecen en complejidad, los eventos nativos del DOM (Document Object Model) pueden quedarse cortos. ¿Qué sucede cuando necesitamos que diferentes partes de nuestra aplicación se comuniquen entre sí sin estar directamente conectadas? Aquí es donde entran en juego los eventos personalizados, una herramienta increíblemente poderosa que te permite crear tu propio sistema de comunicación interno, haciendo tu código más limpio, modular y fácil de mantener.

¿Por qué los Eventos Nativos No Siempre Son Suficientes?
Los eventos estándar como click, submit o keydown son fantásticos para responder a las interacciones del usuario con la interfaz. Pero su alcance está limitado al DOM. Piensa en situaciones puramente lógicas de tu aplicación:
- Un usuario inicia sesión y varias partes de la UI necesitan actualizarse (el avatar en la cabecera, el mensaje de bienvenida, el menú de opciones).
- Los datos de un producto se cargan desde una API y un carrito de compras necesita saber si el stock ha cambiado.
- Un flujo de varios pasos es completado por el usuario y necesitas activar una notificación de éxito.
En estos casos, no hay un "evento" de DOM que represente estas acciones. Podríamos solucionarlo con callbacks o acoplando directamente los componentes, pero esto a menudo conduce a un código enrevesado y frágil, conocido como "código espagueti". La solución más elegante es crear nuestros propios eventos que representen estas acciones específicas de la aplicación.
Creando y Despachando Eventos con la API Nativa
JavaScript nos proporciona una forma estándar y muy sencilla de crear nuestros propios eventos a través del constructor CustomEvent. Este método es ideal cuando la comunicación que necesitas está ligada a elementos del DOM.
Paso 1: Crear el Evento
Para crear un evento, utilizamos el constructor new CustomEvent(). Este recibe dos argumentos: el nombre del evento (una cadena de texto que tú inventas) y un objeto de opciones.
// Creamos un evento llamado 'profileUpdated' const profileUpdateEvent = new CustomEvent('profileUpdated', { detail: { username: 'JuanDev', level: 15 } });La propiedad más importante en el objeto de opciones es detail. Este es un objeto donde puedes colocar cualquier dato que quieras enviar junto con el evento. Es la forma de pasar información desde el punto donde se origina el evento hasta el punto donde se escucha.

Paso 2: Despachar (Disparar) el Evento
Una vez creado el evento, necesita ser "disparado" desde un elemento del DOM. Para ello, utilizamos el método dispatchEvent() en el elemento que queramos que sea el emisor.
// Seleccionamos el contenedor del perfil del usuario const userProfileElement = document.querySelector('#user-profile'); // Disparamos el evento desde ese elemento userProfileElement.dispatchEvent(profileUpdateEvent);Ahora, el evento profileUpdated ha sido emitido desde el elemento #user-profile.
Paso 3: Escuchar el Evento
Finalmente, cualquier otra parte de tu código puede escuchar este evento utilizando el conocido método addEventListener(), exactamente igual que lo harías con un evento de clic.
// El componente de la cabecera escucha el evento const headerElement = document.querySelector('header'); headerElement.addEventListener('profileUpdated', (event) => { console.log('¡El perfil se ha actualizado!'); // Accedemos a los datos enviados a través de event.detail const { username, level } = event.detail; // Actualizamos la UI de la cabecera document.querySelector('#header-username').textContent = `Hola, ${username} (Nivel ${level})`; });¡Y listo! El componente de la cabecera se ha actualizado sin tener conocimiento directo de la lógica que actualizó el perfil. Esto es desacoplamiento en acción.

Opciones Avanzadas del Constructor
El objeto de opciones de CustomEvent permite configurar el comportamiento del evento:
- bubbles: Un booleano (
falsepor defecto) que, si se establece entrue, hace que el evento se propague hacia arriba en el árbol del DOM, desde el elemento origen hacia sus padres, abuelos, etc. - cancelable: Un booleano (
falsepor defecto) que, si se establece entrue, permite que el evento sea cancelado usandoevent.preventDefault(). - composed: Un booleano (
falsepor defecto) que permite que el evento atraviese los límites del Shadow DOM, algo crucial cuando se trabaja con Web Components.
Construyendo tu Propio Sistema de Eventos (Patrón Pub/Sub)
A veces, nuestros eventos no están relacionados con ningún elemento del DOM. Son eventos puramente lógicos que ocurren en el backend de nuestra aplicación de navegador. Para estos casos, construir un sistema de eventos simple, a menudo llamado emisor de eventos (Event Emitter) o patrón Publicador/Suscriptor (Pub/Sub), es una excelente alternativa.
La idea es tener un objeto central que gestione los eventos. Los componentes pueden "suscribirse" a un evento y el código que lo genera puede "publicarlo" a través de este objeto central.
Implementación de un `EventEmitter` Básico
Podemos crear una clase simple en JavaScript para manejar esta lógica.
class EventEmitter { constructor() { this.listeners = {}; } // Suscribirse a un evento on(event, listener) { if (!this.listeners[event]) { this.listeners[event] = []; } this.listeners[event].push(listener); } // Darse de baja de un evento off(event, listener) { if (!this.listeners[event]) return; this.listeners[event] = this.listeners[event].filter( (l) => l !== listener ); } // Emitir o publicar un evento emit(event, ...args) { if (!this.listeners[event]) return; this.listeners[event].forEach((listener) => { listener(...args); }); } }Uso del `EventEmitter`
Ahora, podemos crear una instancia de nuestro emisor y usarla en toda la aplicación para comunicarnos.

// Creamos una instancia global para nuestra app const appEvents = new EventEmitter(); // --- En el Módulo de Notificaciones --- function mostrarNotificacion(mensaje, tipo) { console.log(`NOTIFICACIÓN [${tipo}]: ${mensaje}`); } appEvents.on('mostrar-notificacion', mostrarNotificacion); // --- En el Módulo de Autenticación --- function iniciarSesion(usuario, password) { // ... lógica de inicio de sesión if (loginExitoso) { // Emitimos el evento con datos appEvents.emit('mostrar-notificacion', 'Inicio de sesión correcto', 'exito'); } else { appEvents.emit('mostrar-notificacion', 'Credenciales incorrectas', 'error'); } } iniciarSesion('usuario', 'pass123'); // Esto disparará la notificaciónComo puedes ver, el módulo de autenticación no sabe nada sobre el módulo de notificaciones. Simplemente emite una señal al sistema central, y cualquier módulo suscrito a esa señal reaccionará. Esto es ideal para una modularidad y escalabilidad máximas.
Tabla Comparativa: API Nativa vs. Sistema Propio
¿Cuándo deberías usar uno u otro? Aquí tienes una comparación para ayudarte a decidir.
| Característica | API Nativa (CustomEvent) | Sistema Propio (EventEmitter) |
|---|---|---|
| Dependencia | Requiere un elemento del DOM para despachar y escuchar. | Independiente del DOM. Basado en un objeto JavaScript. |
| Caso de Uso Principal | Comunicación entre componentes de la UI, Web Components. | Comunicación de lógica de aplicación, gestión de estado. |
| Paso de Datos | A través de la propiedad event.detail. | Como argumentos adicionales en el método emit(). |
| Complejidad | Muy bajo. Ya está integrado en el navegador. | Baja. Requiere implementar o importar una pequeña clase. |
Preguntas Frecuentes (FAQs)
- ¿Por qué no usar siempre
addEventListenerpara todo? addEventListeneres para escuchar eventos, no para crearlos. Es perfecto para eventos de DOM nativos, pero para la comunicación entre módulos de tu propia lógica, necesitas algo que emita esas señales. Los eventos personalizados son precisamente esas señales que luego puedes escuchar conaddEventListener(si usas la API nativa).- ¿Puedo usar un sistema de eventos personalizado en una aplicación grande como las construidas con React o Vue?
- ¡Absolutamente! De hecho, los frameworks modernos usan conceptos similares internamente. Vue tiene un sistema de eventos con
$emity$on. En React, aunque el flujo de datos es más unidireccional, se pueden usar librerías externas de gestión de estado o emisores de eventos para comunicar componentes no relacionados directamente. - ¿Cómo puedo priorizar los "listeners" o escuchas?
- En nuestra implementación básica de
EventEmitter, los listeners se ejecutan en el orden en que se registraron. Si necesitaras prioridades, podrías modificar el métodoon()para aceptar un argumento de prioridad y almacenar los listeners en un array ordenado por ese valor antes de ejecutarlos en el métodoemit(). - ¿Cuál es la ventaja de un listener de un solo uso?
- A veces, solo necesitas reaccionar a un evento una única vez, por ejemplo, para una inicialización. Un método
once()(que puedes añadir a la claseEventEmitter) registraría un listener que se elimina automáticamente después de su primera ejecución, evitando fugas de memoria y ejecuciones no deseadas.
Conclusión
Dominar los eventos personalizados en JavaScript, ya sea a través de la API nativa CustomEvent o construyendo tu propio EventEmitter, es un paso fundamental para escribir código más profesional, desacoplado y mantenible. Te permiten crear un sistema de comunicación robusto donde las diferentes partes de tu aplicación pueden hablar entre sí sin crear una red de dependencias frágiles. La próxima vez que te encuentres pasando callbacks a través de múltiples niveles de componentes, detente y pregúntate: ¿podría un evento personalizado simplificar esto? La respuesta, muy probablemente, será que sí.
Si quieres conocer otros artículos parecidos a JavaScript: Crea tus Propios Eventos Personalizados puedes visitar la categoría Juegos.
