Trabajo repetido dentro del bucle
Actualizar un contador en cada iteración cuando el total ya se sabe de golpe.
- debounce
- batching
- trabajo dentro del bucle
El problema
Un buscador que filtra artículos y actualiza un contador de resultados.
Parece razonable llevar la cuenta dentro del bucle: cada vez que se
añade una fila, se suma uno. Pero el contador no necesita saber cuántos
lleva, solo cuántos hay al final — y al final ya lo dice .length.
Y como bono, este código repite dos veces el problema del artículo
anterior: filtra en cada tecla e incendia el DOM con innerHTML +=
dentro del bucle.
El código “roto”
const input = document.getElementById("buscador");
const tbody = document.getElementById("filas");
const contador = document.getElementById("total");
const articulos = [
{ titulo: "Análisis del Pixel", categoria: "Móviles" },
{ titulo: "Guía de teclados", categoria: "Periféricos" },
{ titulo: "Qué es RISC-V", categoria: "Hardware" },
{ titulo: "Review SSD Gen5", categoria: "Almacenamiento" },
];
input.addEventListener("input", (e) => {
const query = e.target.value.toLowerCase();
tbody.innerHTML = "";
let total = 0;
articulos.forEach((art) => {
if (art.titulo.toLowerCase().includes(query)) {
tbody.innerHTML += `<tr><td>${art.titulo}</td><td>${art.categoria}</td></tr>`;
total++;
contador.textContent = "Resultados: " + total;
}
});
});
Las pistas
- Debounce fuera, filtrado dentro.
- El total se conoce de golpe una vez filtrado.
.lengthlo tiene. - Una sola asignación a
innerHTMLconmap(...).join("").
Ver la solución
const input = document.getElementById("buscador");
const tbody = document.getElementById("filas");
const contador = document.getElementById("total");
const articulos = [
{ titulo: "Análisis del Pixel", categoria: "Móviles" },
{ titulo: "Guía de teclados", categoria: "Periféricos" },
{ titulo: "Qué es RISC-V", categoria: "Hardware" },
{ titulo: "Review SSD Gen5", categoria: "Almacenamiento" },
];
let timeout;
input.addEventListener("input", (e) => {
clearTimeout(timeout);
timeout = setTimeout(() => {
const query = e.target.value.toLowerCase();
const filtrados = articulos.filter((art) => art.titulo.toLowerCase().includes(query));
tbody.innerHTML = filtrados.map((art) => `<tr><td>${art.titulo}</td><td>${art.categoria}</td></tr>`).join("");
contador.textContent = "Resultados: " + filtrados.length;
}, 200);
});El cambio de fondo es sacar el contador fuera del bucle. Antes se
recalculaba en cada iteración; ahora se calcula una sola vez porque
filtrados.length ya lo sabe. Es la misma idea que el ejercicio 7
(insertar en el DOM dentro de un bucle): todo el trabajo repetido que
se puede hacer una sola vez debe hacerse una sola vez.
El resto son patrones ya conocidos: debounce (ejercicio 2) para no
filtrar en cada tecla, y map(...).join("") en lugar de innerHTML +=
para no reparsear el tbody en cada iteración.
Contador dentro del bucle vs con .length
Este ejercicio suma un problema encima del anterior: el contador de resultados se actualiza en cada iteración del bucle. Como el navegador solo repinta el valor final, la tabla y el contador se ven idénticos en las dos versiones. La diferencia está en la stat"Updates al contador DOM" — en sin optimizarcrece con cada match; en optimizado, uno por filtro.
| Elemento |
|---|
| Elemento |
|---|
Comprueba lo que has aprendido
1. ¿Por qué actualizar `contador.textContent` dentro del bucle es un problema?
2. ¿Qué propiedad usarías para obtener el total del filtro directamente, sin llevar cuenta manual?
3. ¿Por qué combinar debounce con `tbody.innerHTML = ...` funciona bien aquí?
4. El código roto tiene `innerHTML +=` dentro del bucle. ¿Qué implica eso?
5. ¿Cuál es el principio general que enseña este ejercicio?