Otimizar a execução do JavaScript

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

Paul Lewis
Paul Lewis

Muitas vezes, o JavaScript aciona mudanças visuais. Às vezes, isso é feito diretamente por meio de manipulações de estilo e, às vezes, cálculos que resultam em mudanças visuais, como pesquisa ou classificação de dados. O JavaScript de longa duração ou no momento errado é uma causa comum de problemas de desempenho. Procure minimizar o impacto dele sempre que possível.

A criação de perfis de desempenho do JavaScript pode ser uma arte, porque o JavaScript que você escreve é nada como o código que é realmente executado. Os navegadores modernos usam compiladores JIT e todos os tipos de otimizações e truques para oferecer a execução mais rápida possível, e isso muda substancialmente a dinâmica do código.

No entanto, existem algumas coisas que você pode fazer para ajudar seus apps a executar bem o JavaScript.

Resumo

  • Evitar setTimeout ou setInterval para mudanças visuais: sempre use 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 Timeline e o JavaScript Profiler do Chrome DevTools para avaliar o impacto do JavaScript.

Usar requestAnimationFrame para mudanças visuais

Quando mudanças visuais estão acontecendo na tela, o ideal é fazer o trabalho no momento certo para o navegador, ou seja, 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 exemplos podem usar setTimeout ou setInterval para fazer mudanças visuais como animações, mas o problema com isso é que o callback vai ser executado em algum ponto do frame, possivelmente no final. Muitas vezes, isso pode fazer com que percamos um frame, resultando em instabilidade.

setTimeout faz o navegador perder um frame.

Na verdade, o jQuery usava setTimeout para o comportamento animate. Mudamos para usar requestAnimationFrame na versão 3. Se você estiver usando uma versão mais antiga da jQuery, é possível corrigi-la para usar requestAnimationFrame, o que é altamente recomendado.

Reduza a complexidade ou use Web Workers

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

Seja tático quanto ao momento e ao tempo de execução do JavaScript. Por exemplo, se você estiver usando uma animação como rolagem, mantenha o JavaScript no período de 3 a 4ms. Uma duração maior do que isso pode levar muito tempo. Se você estiver em um período de inatividade, pode se dar ao luxo de ser mais relaxado.

Em muitos casos, você pode mover o trabalho computacional puro para os Web Workers, se, por exemplo, o acesso ao DOM não for necessário. A manipulação ou travessia de dados, como classificar ou pesquisar, 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 todos os trabalhos podem 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 em lote, em que a tarefa maior é segmentada em microtarefas. Cada uma delas não demora mais do que alguns milissegundos e é executada dentro de gerenciadores requestAnimationFrame em cada frame.

Essa abordagem tem consequências na IU e na experiência do usuário, e você precisa garantir que o usuário saiba que uma tarefa está sendo processada, usando um indicador de progresso ou de atividade. De qualquer forma, essa abordagem manterá a linha de execução principal do app livre, ajudando a permanecer responsiva às interações do usuário.

Conheça a "taxa de frames" do JavaScript

Ao avaliar um framework, uma biblioteca ou seu próprio código, é importante avaliar quanto custa executar o código JavaScript frame a frame. Isso é especialmente importante ao realizar trabalhos de animação de desempenho crítico, como transição ou rolagem.

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

Gravação de performance no Chrome DevTools

Na seção Main, há um diagrama de chamas de chamadas JavaScript para que você possa analisar exatamente quais funções foram chamadas e quanto tempo cada uma levou.

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

Consulte Introdução à análise do desempenho no momento da execução para saber como usar o painel "Desempenho".

Evite a microotimização do seu JavaScript

Pode ser legal saber que o navegador pode executar uma versão de algo 100 vezes mais rápido do que outra. Por exemplo, solicitar o offsetTop de um elemento é mais rápido do que calcular getBoundingClientRect(), mas é quase sempre verdade que as funções como essas são feitas poucas vezes por frame. Por isso, normalmente é desperdiçado se concentrar nesse aspecto do desempenho do JavaScript. Normalmente, você só economizará frações de milissegundos.

Se você está criando um jogo ou um aplicativo de alto custo computacional, provavelmente é uma exceção a essa orientação, já que normalmente há muita computação em um único frame. Nesse caso, tudo ajuda.

Resumindo, é necessário ter muito cuidado com as microotimizações, porque elas normalmente não são mapeadas para o tipo de aplicativo que você está criando.