¿Tu sitio va lento? Es hora de perfilar JavaScript
\n¿Alguna vez has sentido ese dolor: el sitio carga, las animaciones son fluidas, pero cuando el usuario comienza a interactuar activamente con la página, todo se congela? Los botones no responden, el scroll se entrecorta y la consola está en silencio. ¿Te suena? Lo más probable es que el problema esté en el rendimiento de JavaScript. Este lenguaje es el alma de la web moderna, pero sin la optimización adecuada puede convertir tu obra maestra en un monstruo lento.
\nEn este artículo no daremos aburridas lecciones. Tomaremos código real, lo analizaremos a fondo con perfilamiento y mostraremos cómo exprimir al máximo cada ciclo. ¿Listo para convertir tu sitio en un bólido de Fórmula 1? ¡Vamos allá!
\n\nPor qué JavaScript va lento: la raíz del mal
\nAntes de agarrar las herramientas, entendamos qué está robando rendimiento. Normalmente, los culpables son tres:
\n- \n
- Bloqueo del hilo principal — JavaScript funciona en un solo hilo (Event Loop). Si ejecutas una operación síncrona pesada, la interfaz se congelará hasta que termine. \n
- Fugas de memoria — manejadores de eventos olvidados, referencias cerradas y variables globales. La memoria crece, el sitio se ralentiza y luego colapsa. \n
- Algoritmos ineficientes — bucles dobles sobre arrays enormes, manipulaciones frecuentes del DOM, estructuras de datos no óptimas. \n
Pero basta de teoría. Veamos cómo se ve esto en la práctica.
\n\nHerramientas de perfilamiento: tu detector de mentiras
\nChrome DevTools — la navaja suiza del perfilador
\nAbre cualquier página, presiona F12 y ve a la pestaña Performance. Presiona el botón de grabación, realiza una acción en la página (por ejemplo, haz clic en un botón) y detén la grabación. Verás una línea de tiempo donde cada segundo está desglosado por fotogramas. Las barras rojas son tus enemigas. Significan que el navegador no logró renderizar un fotograma en 16.6 ms (60 FPS).
\n// Ejemplo: una función simple pero problemática\nfunction heavyCalculation() {\n let result = 0;\n for (let i = 0; i < 100000000; i++) {\n result += Math.sqrt(i) * Math.sin(i);\n }\n return result;\n}\n\ndocument.getElementById('btn').addEventListener('click', () => {\n const start = performance.now();\n heavyCalculation(); // ¡La interfaz se congelará por 2-3 segundos!\n console.log('Tiempo:', performance.now() - start);\n});\n\nEjecuta el perfilamiento y haz clic en el botón. Verás que el hilo principal está ocupado con la llamada a heavyCalculation durante un segundo entero. Eso es un cuello de botella.
Memory — cacería de fugas
\nVe a la pestaña Memory y toma una instantánea del heap. Luego realiza una acción en la página varias veces y toma una segunda instantánea. Compáralas. Si la cantidad de objetos de un mismo tipo crece sin parar, tienes una fuga.
\n// Fuga clásica: manejador olvidado\nfunction createButton() {\n const btn = document.createElement('button');\n btn.textContent = 'Click me';\n document.body.appendChild(btn);\n \n // El manejador mantiene una referencia a btn, incluso si btn es eliminado\n btn.addEventListener('click', function onClick() {\n console.log('Clicked');\n });\n \n // Eliminamos el botón, pero el manejador permanece en memoria\n document.body.removeChild(btn);\n}\n\n\nTécnicas de optimización: de la teoría a la práctica
\n1. Aligeramos el hilo principal con async/await
\nSi tienes una tarea pesada, no bloquees el hilo. Usa Web Workers o divide la tarea en microtareas. Pero la forma más simple es setTimeout con retardo cero o requestIdleCallback.
// Malo: carga síncrona\nfunction loadBigData() {\n const data = [];\n for (let i = 0; i < 1000000; i++) {\n data.push({ id: i, value: Math.random() });\n }\n render(data);