Articulo publicado el
Maneja eventos en múltiples elementos con un único listener y mejora el rendimiento de tu aplicación
Event delegation es una técnica en JavaScript que aprovecha el event bubbling (propagación de eventos) para manejar eventos en múltiples elementos usando un único listener. En lugar de añadir un listener a cada elemento individual, lo añades a un contenedor padre y esperas a que los eventos "burbujeen" hacia arriba.
Esta técnica es especialmente útil cuando trabajas con elementos dinámicos, listas largas o aplicaciones que crean elementos en tiempo de ejecución. Sin event delegation, tendrías que añadir listeners cada vez que creas un nuevo elemento, lo cual es ineficiente y propenso a errores.
Imagina que tienes una lista de tareas donde el usuario puede eliminar elementos:
<ul id="taskList">
<li><span class="task-name">Comprar leche</span> <button class="delete-btn">Eliminar</button></li>
<li><span class="task-name">Pagar facturas</span> <button class="delete-btn">Eliminar</button></li>
<li><span class="task-name">Estudiar JavaScript</span> <button class="delete-btn">Eliminar</button></li>
</ul>
Sin event delegation, necesitarías algo así:
const deleteButtons = document.querySelectorAll(".delete-btn");
deleteButtons.forEach((button) => {
button.addEventListener("click", function () {
this.parentElement.remove();
});
});
El problema es que si añades nuevas tareas dinámicamente, los nuevos botones no tendrán el listener:
// Este botón NO tendrá el listener de eliminación
const newTask = document.createElement("li");
newTask.innerHTML = '<span class="task-name">Nueva tarea</span> <button class="delete-btn">Eliminar</button>';
document.getElementById("taskList").appendChild(newTask);
Con event delegation, añades un único listener al elemento padre y usas event.target para identificar en qué elemento se hizo clic:
const taskList = document.getElementById("taskList");
taskList.addEventListener("click", function (event) {
// Verificar si el elemento clickeado es un botón de eliminar
if (event.target.classList.contains("delete-btn")) {
// Eliminar la tarea (elemento padre del botón)
event.target.parentElement.remove();
}
});
Ahora, cuando añadas nuevas tareas, los botones funcionarán automáticamente sin necesidad de añadir listeners adicionales:
const newTask = document.createElement("li");
newTask.innerHTML = '<span class="task-name">Nueva tarea</span> <button class="delete-btn">Eliminar</button>';
document.getElementById("taskList").appendChild(newTask); // ¡Funciona sin configuración adicional!
Usar un único listener es mucho más eficiente que tener múltiples listeners. Menos listeners significa menos consumo de memoria y mejor rendimiento, especialmente en listas con cientos o miles de elementos.
El impacto en memoria es exponencial con el número de elementos. Un listener ocupa aproximadamente 100-200 bytes en memoria, por lo que con 1000 elementos adicionales, estarías gastando 100-200KB innecesarios.
// Mal: 1000 listeners
const items = document.querySelectorAll(".item");
items.forEach((item) => {
item.addEventListener("click", handler);
});
// Bien: 1 listener
const container = document.getElementById("container");
container.addEventListener("click", function (event) {
if (event.target.classList.contains("item")) {
handler(event);
}
});
Una de las mayores ventajas es que no necesitas actualizar los listeners cuando creas nuevos elementos. El listener en el contenedor padre capturará los eventos de cualquier elemento hijo, aunque se cree después de que el listener se haya instalado.
Con event delegation, el código es más conciso y fácil de mantener. No tienes que preocuparte por limpiar listeners o attachear nuevos listeners a nuevos elementos.
focus, blur, load, unload (estos no se propagan hacia arriba)Aquí hay un ejemplo de una aplicación de tareas usando event delegation:
class TaskApp {
constructor(listSelector) {
this.list = document.querySelector(listSelector);
this.init();
}
init() {
// Un único listener para toda la lista
this.list.addEventListener("click", (event) => this.handleClick(event));
}
handleClick(event) {
const deleteBtn = event.target.closest(".delete-btn");
const editBtn = event.target.closest(".edit-btn");
if (deleteBtn) {
this.deleteTask(deleteBtn);
} else if (editBtn) {
this.editTask(editBtn);
}
}
deleteTask(button) {
button.closest("li").remove();
}
editTask(button) {
const taskName = button.closest("li").querySelector(".task-name");
const newName = prompt("Nuevo nombre:", taskName.textContent);
if (newName) {
taskName.textContent = newName;
}
}
}
// Usar la aplicación
const app = new TaskApp("#taskList");
closest() para capasEl método closest() es tu amigo en event delegation. Te permite encontrar el ancestro más cercano que coincida con un selector, lo cual es más robusto que verificar event.target directamente:
// Frágil: Asume que el botón es el elemento clickeado
if (event.target.classList.contains("delete-btn")) {
}
// Robusto: Funciona incluso si haces clic en un icono dentro del botón
const button = event.target.closest(".delete-btn");
if (button) {
}
Las aplicaciones de tareas son un caso perfecto para event delegation. Tienes una lista que crece dinámicamente y múltiples acciones por item (eliminar, editar, completar, etc.).
Cuando renderizas tablas desde API responses, la event delegation evita que tengas que attachear listeners a cada fila:
const tbody = document.querySelector("table tbody");
tbody.addEventListener("click", function (event) {
const row = event.target.closest("tr");
if (!row) return;
const editBtn = event.target.closest(".edit-btn");
const deleteBtn = event.target.closest(".delete-btn");
if (editBtn) editRow(row);
if (deleteBtn) deleteRow(row);
});
Cuando construyes componentes reutilizables que se instancian múltiples veces en la página, event delegation simplifica significativamente la gestión de eventos.
Comparemos el rendimiento entre sin event delegation y con event delegation:
// Scenario: 1000 elementos con listeners individuales
console.time("Sin Event Delegation");
const elements = document.querySelectorAll(".item");
elements.forEach((el) => {
el.addEventListener("click", () => {});
});
console.timeEnd("Sin Event Delegation"); // ~15-25ms
console.time("Con Event Delegation");
const container = document.getElementById("container");
container.addEventListener("click", (e) => {
if (e.target.classList.contains("item")) {
}
});
console.timeEnd("Con Event Delegation"); // ~1-2ms
En aplicaciones con miles de elementos, la diferencia de rendimiento es muy notable, especialmente en dispositivos móviles o con menos recursos.
La respuesta es usar event delegation por defecto en listas dinámicas y contenedores con múltiples elementos interactivos. Solo evítala si tienes eventos especiales que no burbujean o necesitas listeners muy específicos.
La event delegation es una técnica fundamental en JavaScript que te permite:
Recuerda usar event.target.closest() para una captura robusta de elementos y aplica esta técnica siempre que trabajes con listas dinámicas o elementos creados en tiempo de ejecución.
Estos articulos pueden interesarte si te gusto este articulo.
Optimiza el rendimiento de tu aplicación limitando cuántas veces se ejecutan listeners de eventos y funciones costosas
Cómo reducir listeners y mejorar el rendimiento usando bubbling de eventos
Aprende a usar los atributos data-* para guardar información en elementos HTML y accederla desde JavaScript de forma segura y semántica