A configuração de innerHTML é síncrona, assim como a maioria das alterações que você pode fazer no DOM. No entanto, renderizar a página da web é uma história diferente.
(Lembre-se de que DOM significa "Document Object Model". É apenas um "modelo", uma representação de dados. O que o usuário vê na tela é uma imagem de como esse modelo deve ser. Portanto, mudar o modelo não é instantâneo alterar a imagem - leva algum tempo para atualizar.)
A execução do JavaScript e a renderização da página da Web acontecem separadamente. Para colocá-lo de forma simplista, em primeiro lugar todo o JavaScript na página runs (do ciclo de eventos - confira este excelente vídeo para mais detalhes) e, em seguida, depois que o navegador processa quaisquer alterações à página da Web para o utilizador ver. É por isso que "bloquear" é tão importante - a execução de código computacionalmente intensivo evita que o navegador passe da etapa "executar JS" e entre na etapa "renderizar a página", fazendo com que a página congele ou gagueje.
O pipeline do Chrome é semelhante a este:
Como você pode ver, todo o JavaScript acontece primeiro. Em seguida, a página é estilizada, disposta, pintada e composta - a "renderização". Nem todo esse pipeline executará todos os quadros. Depende de quais elementos da página foram alterados, se houver, e de como eles precisam ser renderizados novamente.
Observação: alert()
também é síncrono e executado durante a etapa de JavaScript, por isso a caixa de diálogo de alerta aparece antes de você ver as alterações na página da web.
Agora você pode perguntar "Espere aí, o que exatamente é executado nessa etapa de 'JavaScript' no pipeline? Todo o meu código é executado 60 vezes por segundo?" A resposta é "não" e remonta ao funcionamento do loop de eventos JS. O código JS só é executado se estiver na pilha - de coisas como ouvintes de eventos, tempos limite, o que for. Veja o vídeo anterior (realmente).
https://developers.google.com/web/fundamentals/performance/rendering/