Portal Performance
dom

Trabajo repetido dentro del bucle

Actualizar un contador en cada iteración cuando el total ya se sabe de golpe.

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

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.

Pruébalo

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.

Sin optimizar · contador dentro del bucle
Filtros: 0 · Updates al contador: 0 · último: — ms
Resultados: 0
Elemento
Optimizado · debounce + .length + map/join
Filtros: 0 · Updates al contador: 0 · último: — ms
Resultados: 0
Elemento

Comprueba lo que has aprendido

  1. 1. ¿Por qué actualizar `contador.textContent` dentro del bucle es un problema?

  2. 2. ¿Qué propiedad usarías para obtener el total del filtro directamente, sin llevar cuenta manual?

  3. 3. ¿Por qué combinar debounce con `tbody.innerHTML = ...` funciona bien aquí?

  4. 4. El código roto tiene `innerHTML +=` dentro del bucle. ¿Qué implica eso?

  5. 5. ¿Cuál es el principio general que enseña este ejercicio?