En esta página
Eventos y delegación
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:
- Captura — Desde
windowhacia abajo hasta el elemento - Target — El elemento que origino el evento
- 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 automaticamentepassive: true— Indica que no llamarapreventDefault(), mejorando el rendimiento en eventos de scroll y touchcapture: 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:
submiten formularios — Evita la recarga de páginaclicken enlaces — Evita la navegaciónkeydown— 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
- Agrega un evento con opciones: Crea un boton en HTML y registrale un evento
clickcon la opcion{ once: true }. Verifica que el handler solo se ejecuta una vez. - Captura un formulario con preventDefault: Crea un formulario con un campo
nombrey un boton submit. UsaaddEventListener('submit', ...)cone.preventDefault()para capturar los datos conFormDatay mostrarlos en consola sin recargar la pagina. - Implementa event delegation: Crea una lista
<ul>con varios<li>que contengan un boton de eliminar. Registra un solo listener en el<ul>y usae.target.closest('.btn-eliminar')para detectar clicks y eliminar el<li>correspondiente.
En la siguiente leccion aprenderemos sobre promesas y programación asincrona.
// === 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');
}
});
// === 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
Inicia sesión para guardar tu progreso