Otimizar a execução do JavaScript

O JavaScript geralmente aciona mudanças visuais. Às vezes, isso acontece diretamente por manipulações de estilo e, às vezes, por cálculos que resultam em mudanças visuais, como pesquisar ou classificar dados. JavaScripts mal programados ou de longa duração são uma causa comum de problemas de desempenho. Procure minimizar o impacto sempre que possível.

O JavaScript geralmente aciona mudanças visuais. Às vezes, isso acontece diretamente por manipulações de estilo e, às vezes, por cálculos que resultam em mudanças visuais, como pesquisar ou classificar dados. O JavaScript mal programado ou de longa duração é uma causa comum de problemas de desempenho. Procure minimizar o impacto sempre que possível.

O perfil de desempenho do JavaScript pode ser uma arte, porque o JavaScript que você escreve não se parece em nada com o código que é realmente executado. Os navegadores modernos usam compiladores JIT e todos os tipos de otimizações e truques para tentar oferecer a execução mais rápida possível. Isso muda significativamente a dinâmica do código.

No entanto, há algumas coisas que você pode fazer para ajudar seus apps a executar JavaScript corretamente.

Resumo

  • Evite usar setTimeout ou setInterval para atualizações visuais. Use sempre requestAnimationFrame.
  • Mova o JavaScript de longa duração da linha de execução principal para os Web Workers.
  • Use microtarefas para fazer mudanças no DOM em vários frames.
  • Use a linha do tempo e o perfilador de JavaScript do Chrome DevTools para avaliar o impacto do JavaScript.

Use requestAnimationFrame para mudanças visuais

Quando as mudanças visuais estão acontecendo na tela, você quer fazer seu trabalho no momento certo para o navegador, que é no início do frame. A única maneira de garantir que o JavaScript seja executado no início de um frame é 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);

Frameworks ou amostras podem usar setTimeout ou setInterval para fazer mudanças visuais, como animações, mas o problema é que o callback será executado em algum ponto do frame, possivelmente no final, e isso pode fazer com que percamos um frame, resultando em instabilidade.

setTimeout faz com que o navegador perca um frame.

Na verdade, o jQuery costumava usar setTimeout para o comportamento animate. Ele foi alterado para usar requestAnimationFrame na versão 3. Se você estiver usando uma versão mais antiga do jQuery, é possível aplicar um patch para usar requestAnimationFrame, o que é altamente recomendável.

Reduzir a complexidade ou usar workers da Web

O JavaScript é executado na linha de execução principal do navegador, junto com cálculos de estilo, layout e, em muitos casos, pintura. Se o JavaScript for executado por muito tempo, ele vai bloquear essas outras tarefas, potencialmente causando a perda de frames.

Você precisa ser tático sobre quando o JavaScript é executado e por quanto tempo. Por exemplo, se você estiver em uma animação como a rolagem, o ideal é manter o JavaScript em algo na região de 3 a 4ms. Se você demorar mais que isso, corre o risco de ocupar muito tempo. Se você estiver em um período de inatividade, pode se sentir mais relaxado em relação ao tempo que leva.

Em muitos casos, é possível mover o trabalho computacional puro para Web Workers, se, por exemplo, ele não exigir acesso ao DOM. A manipulação ou a travessia de dados, como classificação ou pesquisa, geralmente são boas opções para esse modelo, assim como o carregamento e a geração 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...
});

Nem todo trabalho pode se encaixar nesse modelo: os Web Workers não têm acesso ao DOM. Quando seu trabalho precisa estar na linha de execução principal, considere uma abordagem de lote, em que você segmenta a tarefa maior em microtarefas, cada uma com duração de alguns milissegundos, e executa dentro de manipuladores requestAnimationFrame em cada frame.

Essa abordagem tem consequências para a UX e a interface, e você precisa garantir que o usuário saiba que uma tarefa está sendo processada, usando um indicador de progresso ou atividade. De qualquer forma, essa abordagem vai manter a linha de execução principal do app livre, ajudando-o a continuar respondendo às interações do usuário.

Conheça a "taxa de frame" do JavaScript

Ao avaliar um framework, uma biblioteca ou seu próprio código, é importante avaliar quanto custa executar o código JavaScript em cada frame. Isso é especialmente importante ao fazer trabalhos de animação críticos para o desempenho, como transições ou rolagem.

O painel "Performance" do Chrome DevTools é a melhor maneira de medir o custo do JavaScript. Normalmente, você recebe registros de baixo nível como este:

Uma gravação de desempenho no Chrome DevTools

A seção Main mostra um gráfico de chamas das chamadas JavaScript para que você possa analisar exatamente quais funções foram chamadas e quanto tempo cada uma levou.

Com essas informações, é possível avaliar o impacto de performance do JavaScript no seu aplicativo e começar a encontrar e corrigir os pontos de acesso em que as funções estão demorando muito para ser executadas. Como mencionado anteriormente, você precisa remover o JavaScript de longa duração ou, se isso não for possível, movê-lo para um Web Worker, liberando a linha de execução principal para continuar com outras tarefas.

Consulte Começar a analisar o desempenho de execução para saber como usar o painel "Performance".

Evite microotimizar seu JavaScript

É legal saber que o navegador pode executar uma versão de uma coisa 100 vezes mais rápido que outra, como solicitar o offsetTop de um elemento é mais rápido que o cálculo getBoundingClientRect(), mas quase sempre é verdade que você só vai chamar funções como essas um pequeno número de vezes por frame. Portanto, é normal desperdiçar esforço para se concentrar nesse aspecto do desempenho do JavaScript. Normalmente, você só economiza frações de milissegundos.

Se você estiver criando um jogo ou um aplicativo computacionalmente caro, provavelmente será uma exceção a essa orientação, já que normalmente vai precisar encaixar muitas computações em um único frame. Nesse caso, tudo ajuda.

Em resumo, você precisa ter muito cuidado com as microotimizações, porque elas geralmente não são mapeadas para o tipo de aplicativo que você está criando.