Optimiza la ejecución de JavaScript

JavaScript a menudo activa cambios visuales. Algunas veces, lo hace directamente mediante manipulaciones de estilo y, otras veces, mediante cálculos que generan cambios visuales, como la búsqueda o clasificación de datos. El JavaScript sincronizado incorrectamente o de larga ejecución es una causa común de los problemas de rendimiento. Debes intentar minimizar su impacto siempre que sea posible.

Paul Lewis
Paul Lewis

JavaScript a menudo activa cambios visuales. Algunas veces, lo hace directamente mediante manipulaciones de estilo y, otras veces, mediante cálculos que generan cambios visuales, como la búsqueda o clasificación de datos. El JavaScript sincronizado incorrectamente o de larga ejecución es una causa común de los problemas de rendimiento. Debes intentar minimizar su impacto siempre que sea posible.

La generación de perfiles de rendimiento de JavaScript puede ser algo artístico, porque el código JavaScript que escribes no se compara con el código que en realidad se ejecuta. Los navegadores modernos usan compiladores JIT y todo tipo de optimizaciones y trucos para probar y brindarte la ejecución más rápida posible, lo que cambia de forma sustancial la dinámica del código.

Sin embargo, hay algunas medidas que puedes tomar para ayudar a que tus apps ejecuten JavaScript de manera correcta.

Resumen

  • Evita usar setTimeout o setInterval para realizar actualizaciones visuales. En su lugar, usa siempre requestAnimationFrame.
  • Traslada JavaScript de larga ejecución fuera del subproceso principal y hacia los Web Workers.
  • Utiliza microtareas para realizar cambios en el DOM en varios marcos.
  • Utiliza la escala de tiempo de las Herramientas para desarrolladores de Chrome y el generador de perfiles de JavaScript para evaluar el impacto de JavaScript.

Usa requestAnimationFrame para los cambios visuales

Cuando se producen cambios visuales en la pantalla, te recomendamos que hagas tu trabajo en el momento adecuado para el navegador, que es justo al inicio del fotograma. La única forma de garantizar que JavaScript se ejecute al inicio de un fotograma es usar requestAnimationFrame.

/**
    * If run as a requestAnimationFrame callback, this
    * will be run at the start of the frame.
    */
function updateScreen(time) {
    // Make visual updates here.
}

requestAnimationFrame(updateScreen);

Los frameworks o las muestras pueden usar setTimeout o setInterval para realizar cambios visuales, como animaciones, pero el problema es que la devolución de llamada se ejecutará en algún punto del fotograma (posiblemente justo al final) y, a menudo, puede provocar que perdamos un fotograma y se bloquee.

setTimeout hace que el navegador omita un fotograma.

De hecho, jQuery usaba setTimeout para su comportamiento animate. Se cambió para usar requestAnimationFrame en la versión 3. Si usas una versión anterior de jQuery, puedes parcharla para usar requestAnimationFrame, lo cual es muy recomendable.

Reduce la complejidad o usa Web Workers

JavaScript se ejecuta en el subproceso principal del navegador, junto con los cálculos de estilo, el diseño y, en muchos casos, la pintura. Si tu JavaScript se ejecuta durante mucho tiempo, bloqueará estas otras tareas, lo que podría causar que se pierdan fotogramas.

Debes ser táctico respecto del momento en que JavaScript se ejecutará y el tiempo durante el cual se ejecutará. Por ejemplo, si estás en una animación como el desplazamiento, lo ideal sería que busques mantener tu JavaScript en un rango de 3 a 4 ms. Si supera ese límite, corres el riesgo de tomarte demasiado tiempo. Si estás en un período de inactividad, puedes relajarte más acerca del tiempo que te toma.

En muchos casos, puedes trasladar el trabajo de procesamiento puro a los Web Workers si, por ejemplo, no se requiere acceso al DOM. La manipulación o el recorrido de datos, como la ordenación o la búsqueda, a menudo son una buena opción para este modelo, al igual que la carga y la generación de modelos.

var dataSortWorker = new Worker("sort-worker.js");
dataSortWorker.postMesssage(dataToSort);

// The main thread is now free to continue working on other things...

dataSortWorker.addEventListener('message', function(evt) {
    var sortedData = evt.data;
    // Update data on screen...
});

No todo el trabajo puede ajustarse a este modelo: los trabajadores web no tienen acceso al DOM. Si tu trabajo debe estar en el subproceso principal, considera un enfoque por lotes, en el que segmentes la tarea más grande en microtareas, cada una de las cuales no tardará más de unos milisegundos, y la ejecutarás dentro de controladores requestAnimationFrame en cada fotograma.

Este enfoque tiene consecuencias para la UX y la IU, y deberás asegurarte de que el usuario sepa que se está procesando una tarea, ya sea mediante un indicador de progreso o de actividad. En cualquier caso, este enfoque mantendrá libre el subproceso principal de tu app, lo que le permitirá mantener la capacidad de respuesta a las interacciones del usuario.

Conoce el “impuesto al fotograma” de JavaScript

Cuando evalúas un framework, una biblioteca o tu propio código, es importante evaluar cuánto cuesta ejecutar el código JavaScript fotograma por fotograma. Esto es especialmente importante cuando se realizan trabajos de animación críticos para el rendimiento, como las transiciones o el desplazamiento.

El panel Performance de las Herramientas para desarrolladores de Chrome es la mejor manera de medir el costo de JavaScript. Por lo general, obtienes registros de bajo nivel como los siguientes:

Una grabación de la presentación en las Herramientas para desarrolladores de Chrome

En la sección Main, se proporciona un gráfico tipo llama de llamadas de JavaScript para que puedas analizar con exactitud a qué funciones se llamó y cuánto tiempo tardó cada una.

Con esta información, puedes evaluar el impacto en el rendimiento de JavaScript en tu aplicación y comenzar a encontrar y corregir los hotspots en los que las funciones tardan demasiado en ejecutarse. Como se mencionó anteriormente, debes quitar el JavaScript de larga duración o, si eso no es posible, moverlo a un trabajador web para liberar el subproceso principal y continuar con otras tareas.

Consulta Cómo comenzar a analizar el rendimiento del entorno de ejecución para aprender a usar el panel Rendimiento.

Evita la microoptimización de tu JavaScript

Puede ser una buena idea saber que el navegador puede ejecutar una versión de un elemento 100 veces más rápido que otra, por lo que solicitar el offsetTop de un elemento es más rápido que calcular getBoundingClientRect(), pero casi siempre es cierto que solo llamarás a funciones como estas una pequeña cantidad de veces por fotograma, por lo que enfocarse en este aspecto del rendimiento de JavaScript suele ser un esfuerzo desperdiciado. Normalmente, solo ahorrarás fracciones de milisegundos.

Si estás creando un juego o una aplicación costosa en términos de procesamiento, es probable que seas una excepción a esta guía, ya que, por lo general, usarás muchos cálculos en un solo fotograma y, en ese caso, todo será útil.

En resumen, debes ser muy cauteloso con las microoptimizaciones porque, por lo general, no se asignarán al tipo de aplicación que estás creando.