Você sabia que um script
inserido em uma página HTML pode impactar diretamente na performance de carregamento? Pois é, dependendo da posição e como você o fizer, o tempo de carregamento da página será influenciado positiva ou negativamente.
Tradicionalmente inserimos um script
assim:
<script src="script.js"></sript>
Sempre que o parser do HTML encontrar esta linha ele irá pausar a análise, uma requisição será feita solicitando o script
, e então depois disso o seu código será executado.
Uma vez que este processo é concluído o parser continua seu trabalho analisando o resto do HTML.
E bem aí está o problema
Como você pode imaginar, essa operação pode ter um impacto enorme no tempo de carregamento da página.
Se o script
levar um pouco mais de tempo do que o esperado para ser carregado, por exemplo se a rede ou a internet do computador ou do celular estiver um pouco lenta, o visitante verá uma página em branco até que o script seja carregado e executado.
A posição importa!
Como eu já disse, quando o parser encontra a linha ele faz o fetch do script
e após isso ele o executa. Somente depois de finalizar esse processo ele volta a analisar o HTML.
Isso é muito ruim, pois, começa a surgir um atraso no carregamento da página.
— Tá! E como solucionamos isso?
Isso é simples, uma solução muito usada é inserir o script
ao fim do conteúdo da página, logo antes da tag </body>
.
Ao fazer isso o script é carregado e executado depois que toda a página já foi analisada e carregada, o que é uma grande melhoria em relação à alternativa principal.
Caso você precise dar suporte a browsers realmente muito antigos, isso é o melhor que você poderá fazer. Caso contrário, você poderá usar uma de duas features mais recentes do HTML: async
e defer
.
Async e Defer
Ambos são atributos booleanos e são usados da seguinte maneira:
<script async src="script.js"></sript>
<script defer src="script.js"></sript>
Se você especificar os dois, somente o async
será considerado pelos navegadores modernos, enquanto os browsers que não suportarem async
mas sim defer
, o usarão como fallback do async
.
Para verificar o atual suporte dos browsers, você pode ver no can i use as informações para async e defer.
Vale ressaltar que o uso desses atributos só faz sentido caso você esteja inserindo o script
dentro da tag head
da página. Eles são totalmente inúteis se usados ao fim do body
.
— Ok José, mas qual a diferença entre eles? Como eles funcionam?
A grande diferença está em como cada atributo faz com que o carregamento e execução do script aconteça.
Comparação de performance
Script no head e sem defer ou async
Veja abaixo como a página carrega o script sem nenhum dos atributos e com a chamada no head
da página:

A análise do HTML é pausada até que o script seja recebido e executado. Ao finalizar a etapa do script a análise é retomada.
Script no body e sem defer ou async
Veja abaixo como a página carrega o script sem nenhum dos atributos e com a chamada no fim da tag body
, logo antes de onde ela é fechada:

A análise do HTML é feita sem nenhuma pausa, e quando é finalizada o script é baixado e executado. A análise é feita antes do download do script, de modo que a página seja exibida para o usuário antes do exemplo anterior.
Script no head com async
Veja como uma página carrega um script a partir do head
da página e com o uso do atributo async
:

O script
é baixado de forma assíncrona e quando o download terminar, então, a análise do HTML é pausada para que o script
seja executado, após a execução a análise é retomada.
Script no head com defer
Veja como uma página carrega um script a partir do head
da página e com o uso do atributo defer
:

O script
é baixado de forma assíncrona, porém será executado somente após a conclusão da análise do HTML.
A análise termina como quando colocamos o script
no final da tag body
, mas no geral a execução do script termina bem antes, porque seu download foi feito em paralelo com a análise do HTML.
— Uau! Isso quer dizer que o Defer é o campeão em termos de velocidade?
Sim!
Resumindo:
Async
bloqueia a análise da página, Defer
não!
Ótimo não é mesmo?
Sim! Mas vai devagar, pois, há um outro problema conhecido: O Bloqueio de renderização da página.
Tanto async
quanto defer
não garantem que a renderização da página não será bloqueada. Quem deve garantir isso é o seu código. Por exemplo, tendo certeza de que o código é executado depois do evento onLoad
.
domInteractive
Os scripts marcados com defer
são executados logo após o evento domInteractive
, o que acontece depois que o HTML é carregado, analisado e o DOM é construído.
Neste ponto CSS e imagens ainda precisam ser analisados e carregados.
Feito isso, o navegador emitirá o evento domComplete
e, em seguida, onLoad
.
domInteractive
é importante porque seu tempo é reconhecido como uma medida de percepção da velocidade de carregamento. Você pode saber mais no MDN.
Ei! Respeite a ordem de execução que eu defini.
Um outro pró do defer
: scripts marcados como async
são executados em ordem “aleatória”, na verdade, na medida em que vão ficando disponíveis. Já os marcados com defer
são executados (após a conclusão da análise) na ordem em que são definidos na marcação.
— Ainda estou confuso José, me diga de uma vez qual o melhor!
A melhor coisa a se fazer para melhorar o tempo de carregamento da página é fazer a chamada do script
no head
e adicionar o atributo defer
:
<script defer src="script.js"></sript>
Este é o cenário que acionará o evento domInteractive
mais rápido.
Considerando todos os prós do defer
, esta parece ser uma escolha melhor do que async
em muitos cenários.
A não ser, é claro, que você esteja fazendo as coisas conscientemente, e propositalmente querendo que durante a análise da página seu Javascript já esteja sendo executado.
Bom, por hoje é só pessoal.
Dúvidas, críticas ou sugestões são sempre bem vindas.
Até breve!
Traduzido e adaptado de https://flaviocopes.com/javascript-async-defer/