En esta página

Eventos y delegación

12 min lectura TextoCap. 3 — DOM y eventos

Modelo de eventos

JavaScript usa un sistema de eventos para responder a interacciones del usuario y otros sucesos en el navegador. Los eventos se propagan en tres fases:

  1. Captura — Desde window hacia abajo hasta el elemento
  2. Target — El elemento que origino el evento
  3. Burbujeo — Desde el elemento hacia arriba hasta window

Por defecto, los listeners se ejecutan en la fase de burbujeo.

addEventListener

Es la forma estandar de registrar un handler de eventos:

elemento.addEventListener(tipo, handler, opciones);

Tipos de eventos comunes

Evento Se dispara cuando...
click Se hace click en el elemento
dblclick Doble click
mouseenter / mouseleave El cursor entra/sale del elemento
keydown / keyup Se presiona/suelta una tecla
input El valor de un input cambia
change Un input pierde el foco tras cambiar
submit Se envia un formulario
scroll Se hace scroll en la página
focus / blur Un elemento recibe/pierde el foco

Opciones útiles

  • once: true — El handler se ejecuta una sola vez y se remueve automaticamente
  • passive: true — Indica que no llamara preventDefault(), mejorando el rendimiento en eventos de scroll y touch
  • capture: true — Escucha en la fase de captura en lugar de burbujeo

El objeto Event

Cada handler recibe un objeto Event con información del evento:

Propiedad Descripcion
event.target Elemento que origino el evento
event.currentTarget Elemento con el listener
event.type Tipo de evento ('click', 'keydown', etc.)
event.key Tecla presionada (en eventos de teclado)
event.preventDefault() Evita el comportamiento por defecto
event.stopPropagation() Detiene la propagación

preventDefault()

Algunos eventos tienen un comportamiento por defecto del navegador. preventDefault() lo cancela:

  • submit en formularios — Evita la recarga de página
  • click en enlaces — Evita la navegación
  • keydown — Evita atajos del navegador

Event delegation

En lugar de agregar un listener a cada elemento individual, agrega un solo listener al contenedor padre. Cuando un evento ocurre en un hijo, burbujea hasta el padre donde lo capturas.

Ventajas de la delegación

  • Rendimiento — Un solo listener en lugar de cientos
  • Elementos dinámicos — Funciona con elementos agregados despues de registrar el listener
  • Menos memoria — Menos referencias a funciones

El patrón closest()

element.closest(selector) busca hacia arriba en el arbol DOM hasta encontrar un ancestro que coincida con el selector. Es clave para la delegación porque el event.target puede ser un hijo del elemento que te interesa (por ejemplo, un icono dentro de un boton).


Práctica

  1. Agrega un evento con opciones: Crea un boton en HTML y registrale un evento click con la opcion { once: true }. Verifica que el handler solo se ejecuta una vez.
  2. Captura un formulario con preventDefault: Crea un formulario con un campo nombre y un boton submit. Usa addEventListener('submit', ...) con e.preventDefault() para capturar los datos con FormData y mostrarlos en consola sin recargar la pagina.
  3. Implementa event delegation: Crea una lista <ul> con varios <li> que contengan un boton de eliminar. Registra un solo listener en el <ul> y usa e.target.closest('.btn-eliminar') para detectar clicks y eliminar el <li> correspondiente.

En la siguiente leccion aprenderemos sobre promesas y programación asincrona.

closest() es tu amigo
El método closest(selector) busca hacia arriba en el DOM hasta encontrar un ancestro que coincida. Es esencial para event delegation cuando los botones contienen iconos u otros elementos hijos.
No uses onclick en HTML
Evita atributos como onclick="función()" en el HTML. Usar addEventListener separa el comportamiento de la estructura, es más mantenible y permite multiples handlers en el mismo elemento.
javascript
// === AGREGAR EVENTOS ===
const boton = document.querySelector('.btn');

// Evento click
boton.addEventListener('click', (event) => {
  console.log('Click en:', event.target);
  console.log('Tipo:', event.type);
});

// Evento con opciones
boton.addEventListener('click', manejarClick, {
  once: true,   // se ejecuta solo una vez
  passive: true, // mejora rendimiento en scroll/touch
});

// Remover evento
function manejarClick(e) {
  console.log('Clickeado');
}
boton.addEventListener('click', manejarClick);
boton.removeEventListener('click', manejarClick);

// Prevenir comportamiento por defecto
const formulario = document.querySelector('form');
formulario.addEventListener('submit', (e) => {
  e.preventDefault(); // evita recarga de página
  const datos = new FormData(formulario);
  console.log('Nombre:', datos.get('nombre'));
});

// Eventos de teclado
document.addEventListener('keydown', (e) => {
  if (e.key === 'Escape') {
    console.log('Escape presionado');
  }
  if (e.key === 's' && (e.ctrlKey || e.metaKey)) {
    e.preventDefault();
    console.log('Ctrl+S capturado');
  }
});
javascript
// === EVENT DELEGATION ===

// MAL: un listener por cada boton
// document.querySelectorAll('.btn-delete').forEach(btn => {
//   btn.addEventListener('click', () => { ... });
// });

// BIEN: un solo listener en el contenedor
const lista = document.querySelector('.todo-lista');

lista.addEventListener('click', (e) => {
  // Buscar el boton más cercano (incluso si se clickeo un hijo)
  const btnEliminar = e.target.closest('.btn-delete');
  if (btnEliminar) {
    const item = btnEliminar.closest('.todo-item');
    const id = item.dataset.id;
    console.log('Eliminar item:', id);
    item.remove();
    return;
  }

  // Toggle completado
  const checkbox = e.target.closest('.todo-check');
  if (checkbox) {
    const item = checkbox.closest('.todo-item');
    item.classList.toggle('completado');
  }
});

// Funciona con elementos agregados dinamicamente
const nuevoItem = document.createElement('li');
nuevoItem.className = 'todo-item';
nuevoItem.dataset.id = '99';
nuevoItem.innerHTML = `
  <input type="checkbox" class="todo-check">
  <span>Nueva tarea</span>
  <button class="btn-delete">X</button>
`;
lista.appendChild(nuevoItem);
// El evento del contenedor captura clicks en este nuevo item