JavaScript
Eventos, propagación y delegación
Explica el flujo de eventos en el DOM, las fases de captura y burbujeo, y cómo usar delegación para manejar interfaces dinámicas con menos listeners.
- Última actualización
- Actualizada
- Nivel
- Fundamentos
JavaScript
Explica el flujo de eventos en el DOM, las fases de captura y burbujeo, y cómo usar delegación para manejar interfaces dinámicas con menos listeners.
Un evento comunica que ocurrió algo en el navegador: una interacción, cambio de estado, mensaje, carga o acción de una API.
button.addEventListener("click", handleClick);Muchos objetos implementan EventTarget:
document y window.AbortSignal.API principal:
target.addEventListener(type, listener, options);
target.removeEventListener(type, listener, options);
target.dispatchEvent(event);function handleClick(event) {
event.type;
event.target;
event.currentTarget;
event.timeStamp;
}target es el origen del evento. currentTarget es el objeto cuyo listener se está ejecutando.
En eventos que propagan, el recorrido conceptual tiene fases:
window
↓ capture
document
↓
ancestros
↓
target
↑ bubble
ancestros
↑
document
↑
windowNo todos los eventos hacen bubbling.
container.addEventListener(
"click",
handleClick,
{ capture: true },
);El listener participa durante el recorrido hacia el target.
container.addEventListener("click", handleClick);Por defecto, la mayoría de listeners se registran para la fase de target/bubble.
<button data-action="save">
<span>Save</span>
</button>Si haces click en el span:
event.target puede ser el span.event.currentTarget será el botón cuando su listener se ejecute.No asumas que target es el elemento donde registraste la callback.
form.addEventListener("submit", (event) => {
event.preventDefault();
submitWithJavaScript();
});Evita la acción predeterminada cuando el evento es cancelable. No detiene propagación.
event.cancelable;
event.defaultPrevented;event.stopPropagation();Detiene el avance hacia otros nodos, pero no cancela la acción predeterminada.
event.stopImmediatePropagation();También evita otros listeners del mismo target para ese evento.
Usar estas operaciones sin necesidad puede romper componentes, analítica o accesibilidad. Prefiere contratos que convivan con la propagación.
En lugar de un listener por elemento:
list.addEventListener("click", (event) => {
const button = event.target.closest(
"[data-action='remove']",
);
if (!button || !list.contains(button)) {
return;
}
removeProduct(button.dataset.productId);
});El listener del contenedor aprovecha bubbling y funciona también para elementos agregados después.
closest puede encontrar un elemento fuera del contenedor cuando existen estructuras o targets particulares. Comprobar container.contains(match) mantiene la delegación dentro del límite esperado.
button.addEventListener("click", () => save());
button.removeEventListener("click", () => save());No elimina nada: son dos funciones distintas.
function handleClick() {
save();
}
button.addEventListener("click", handleClick);
button.removeEventListener("click", handleClick);target.addEventListener("load", handleLoad, {
once: true,
});Se retira después de la primera ejecución.
target.addEventListener("touchmove", handler, {
passive: true,
});Promete que la callback no llamará preventDefault. Puede ayudar al navegador con gestos y scroll. No lo uses cuando necesitas cancelar.
const controller = new AbortController();
target.addEventListener("click", handler, {
signal: controller.signal,
});
controller.abort();Retira el listener de forma coordinada.
El evento submit pertenece al formulario, no solo al click de un botón.
form.addEventListener("submit", handleSubmit);Esto cubre envío con teclado y otras formas accesibles.
input: cambia mientras se edita.change: se confirma un cambio según el control.No intercambies ambos sin pensar en la experiencia esperada.
input.addEventListener("keydown", (event) => {
if (event.key === "Enter") {
submit();
}
});Usa key para significado y code para la posición física. Respeta composición de texto y comportamientos nativos.
if (event.isComposing) return;Pointer Events unifican mouse, touch y stylus.
element.addEventListener("pointerdown", handler);No asumas que toda persona utiliza mouse ni que un click es la única interacción.
const event = new CustomEvent("cart:updated", {
detail: {
itemCount: 3,
},
bubbles: true,
});
cartElement.dispatchEvent(event);detail transporta información. Un evento interno no debería exponer objetos mutables sensibles sin un contrato claro.
target.dispatchEvent(event);Despacha listeners síncronamente dentro de la llamada actual.
Un click real generado por el navegador llega mediante su sistema de eventos y Event Loop. No todas las callbacks llamadas “event handler” tienen el mismo origen temporal.
Listeners de larga vida pueden conservar closures y nodos.
function mount() {
const controller = new AbortController();
window.addEventListener("resize", handleResize, {
signal: controller.signal,
});
return () => controller.abort();
}table.addEventListener("click", (event) => {
const action = event.target.closest(
"button[data-action]",
);
if (!action || !table.contains(action)) {
return;
}
const row = action.closest("tr[data-product-id]");
if (!row) return;
switch (action.dataset.action) {
case "edit":
editProduct(row.dataset.productId);
break;
case "delete":
deleteProduct(row.dataset.productId);
break;
}
});Un listener maneja acciones dinámicas sin perder contexto.
¿Por qué removeEventListener no funciona cuando recibe una arrow nueva con el mismo cuerpo?
Porque cada expresión crea una función con identidad distinta. La API necesita la misma referencia registrada originalmente.
Fetch API explica solicitudes, respuestas, cuerpos, errores HTTP y cancelación.