Optimiza la ejecución de JavaScript

JavaScript suele activar 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 puede ser una causa común de los problemas de rendimiento. Debes intentar minimizar su impacto siempre que sea posible.

JavaScript suele activar 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 puede ser 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 considerarse como una obra de arte, ya que el código JavaScript que escribes no se parece en nada al código que en realidad se ejecuta. En los navegadores modernos, se usan los compiladores JIT y toda clase de optimizaciones y trucos posibles para realizar pruebas y brindarte la ejecución más rápida posible, y esto modifica en gran medida la dinámica del código.

No obstante, hay algunas medidas que definitivamente puedes tomar para que JavaScript se ejecute correctamente en tus apps.

Resumen

  • Evita usar setTimeout o setInterval para realizar actualizaciones visuales. En su lugar, usa siempre requestAnimationFrame.
  • Desplaza JavaScript de larga ejecución fuera de la cadena principal y hacia los Web Workers.
  • Usa microtareas para realizar cambios en el DOM en varios fotogramas.
  • Usa la escala de tiempo de Chrome DevTools 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 tu código JavaScript se ejecute al inicio de un fotograma es a través de 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);

En los fotogramas o los ejemplos se puede usar setTimeout o setInterval para introducir cambios visuales, como animaciones, pero el problema es que la devolución de llamada se ejecutará en algún momento del fotograma, posiblemente justo al final, y eso, a menudo, puede causar la pérdida de un fotograma, lo cual genera un bloqueo.

setTimeout hace que el navegador omita un fotograma.

De hecho, jQuery solía usar setTimeout para su comportamiento animate. Se cambió para usar requestAnimationFrame en la versión 3. Si usas una versión anterior de jQuery, puedes corregirla para usar requestAnimationFrame, lo que se recomienda enfáticamente.

Reducción de la complejidad o uso de Web Workers

JavaScript se ejecuta en la cadena principal del navegador, exactamente junto con los cálculos de estilo, el diseño y, en muchos casos, la pintura. Si tu JavaScript se ejecuta durante un período prolongado, bloqueará estas otras tareas y posiblemente ocasione la pérdida de fotogramas.

Debes ser estratégico respecto del momento en que JavaScript se ejecutará y el tiempo durante el cual esto se extenderá. Por ejemplo, si estás en una animación como el desplazamiento, lo ideal es que mantengas el ajuste de tu JavaScript dentro del rango de 3 a 4 ms. Cualquier valor superior hará que corras el riesgo de usar demasiado tiempo para esta tarea. Si estás en un período de inactividad, puedes relajarte más respecto del tiempo requerido.

En muchos casos, puedes mover el trabajo específico de cálculo a los Web Workers si, por ejemplo, no se requiere acceso al DOM. La manipulación o el cruce seguro de datos, como la clasificación o la búsqueda, a menudo son muy adecuados 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 todas las tareas se pueden realizar correctamente en este modelo: los Web Workers no tienen acceso al DOM. En aquellos casos en los cuales tu trabajo se deba ejecutar en el subproceso principal, considera implementar un enfoque de procesamiento por lotes mediante el cual se segmente la tarea más grande en microtareas que en cada caso no duren más de algunos milisegundos y se ejecuten dentro de los controladores requestAnimationFrame en cada fotograma.

Este enfoque tiene consecuencias para la experiencia y la interfaz de usuario, y deberás asegurarte de que el usuario sepa que una tarea se encuentra en proceso, ya sea con un indicador de progreso o de actividad. En cualquier caso, este enfoque mantendrá libre el subproceso principal de tu app para que sea adaptable ante 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 también la exigencia que supone ejecutar el código de JavaScript fotograma por fotograma. Esto es de especial importancia cuando se realizan trabajos de animación críticos para el rendimiento, como las transiciones o el desplazamiento.

El panel Rendimiento de Chrome DevTools es la mejor manera de medir el costo de tu JavaScript. Por lo general, obtienes registros de bajo nivel como este:

Una grabación de rendimiento en las herramientas para desarrolladores de Chrome

La sección Principal proporciona un gráfico de llamas de las llamadas a JavaScript para que puedas analizar exactamente a qué funciones se llamó y cuánto tiempo demoró cada una.

Con esta información a tu disposición, puedes evaluar el impacto del rendimiento de JavaScript en tu aplicación y comenzar a detectar y corregir las hotspots en las cuales las funciones demoren tarden en ejecutarse. Como se mencionó anteriormente, debes intentar quitar el JavaScript de larga ejecución o, si esto no fuera posible, moverlo hacia un Web Worker para liberar la cadena principal y continuar con otras tareas.

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

Evita la microoptimización de tu JavaScript

Puede ser interesante saber que el navegador puede ejecutar una versión de algo 100 veces más rápido que otro, como 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, por lo general, es un esfuerzo desperdiciado enfocarse en este aspecto del rendimiento de JavaScript. Por lo general, solo ahorrarás fracciones de milisegundos.

Si creas un juego o una aplicación de alta exigencia en términos de cálculo, tu caso será una excepción para esta orientación porque es muy probable que introduzcas muchos cálculos en un solo fotograma, y en ese caso todo servirá.

En pocas palabras, debes ser muy cauteloso con las microoptimizaciones debido a que, por lo general, no se asignarán al tipo de aplicación que crees.