O innerHTML é assíncrono?

101

Espero não fazer papel de bobo, mas estou tentando entender o que está acontecendo nessas duas linhas de código:

document.body.innerHTML = 'something';
alert('something else');

O que estou observando é que o alerta é mostrado antes que o HTML tenha sido atualizado (ou talvez sim, mas a página não foi atualizada / repintada / qualquer coisa)

Verifique este código para ver o que quero dizer.

Por favor, note que, mesmo colocando alertem setTimeout(..., 0)não ajuda. Parece que são necessários mais loops de eventos para innerHTMLrealmente atualizar a página.

EDITAR:

Esqueci de mencionar que estou usando o Chrome e não verifiquei outros navegadores. Parece que só é visível no Chrome. No entanto, ainda estou interessado por que isso está acontecendo.

cada um da barra
fonte
4
Isso é típico do Chrome.
trincot
2
@trincot obrigado! Esqueci de mencionar que estou usando o Chrome e não tentei outro navegador antes de perguntar. Você tem alguma referência?
apieceofbart
16
A alteração do DOM é síncrona. A renderização do DOM realmente acontece depois que a pilha do JavaScript é limpa. developers.google.com/web/fundamentals/performance/rendering JavaScript> Estilo> Layout> Pintura> Composto. (Pelo menos para o Chrome. Outros navegadores são semelhantes.)
oscilou em
2
apenas um palpite: o bloqueio do alerta evita que o navegador chegue a uma etapa de repintura, portanto, embora o DOM tenha mudado, ele ainda não teve permissão para repintar; novamente, apenas um palpite.
zzzzBov
2
@qbolec confira este vídeo: youtu.be/r8caVE_a5KQ
apieceofbart

Respostas:

130

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:

insira a descrição da imagem aqui

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/

sacudido
fonte
1
obrigado por responder. Eu sabia alguns detalhes sobre o loop de eventos, etc. Este pipeline faz todo o sentido, mas como os exemplos mostram, não é o mesmo em todos os navegadores. Se outros navegadores esperam que a página seja redesenhada antes de mostrar o alerta, isso significa que pode haver um grande atraso entre clicar no botão e mostrar o próprio alerta. Vou tentar brincar com isso mais tarde.
apieceofbart
@apieceofbart Outros navegadores também podem simplesmente redesenhar a página de forma assíncrona enquanto interrompem o conteúdo do javascript até que o usuário lide com a janela de alerta. Isso não significa que eles tenham que esperar a repintura acontecer.
Jonas Schäfer
27

Sim, é síncrono, porque funciona (vá em frente, digite no console):

document.body.innerHTML = 'text';
alert(document.body.innerHTML);// you will see a 'text' alert

O motivo pelo qual você vê o alerta antes de ver a alteração da página é que a renderização do navegador leva mais tempo e não é tão rápida quanto a execução do seu javascript linha por linha.

d -_- b
fonte
Obrigado, eu verifiquei isso. Mas tem que ser algo específico para o Chrome - funciona como esperado em outros navegadores. Ou talvez seja a falha deles esperar a página para redesenhar para mover para a próxima linha de javascript?
apieceofbart
3
O alerta mostra o texto correto? ( textno meu exemplo) Isso vai responder à sua pergunta se é síncrono. A renderização do navegador vs. execução do Javascript é apple and oranges :)
d -_- b
Faz, mas não tem nada a ver com a questão
apieceofbart
1
A sua pergunta é literalmente "O innerHTML é assíncrono?". Se o valor puder ser usado imediatamente após ser síncrono, não? Acho que você quer perguntar mais sobre renderização de página, não sobre a qualidade síncrona de innerHTML.
d -_- b
8
Sim, a pergunta estava claramente errada, mas eu não sabia o que perguntar. Se eu soubesse qual era a certa, provavelmente teria encontrado a resposta. Eu não queria ser mau, obrigado pela resposta de qualquer maneira!
apieceofbart
6

A innerHTMLpropriedade real é atualizada de forma síncrona, mas o redesenho visual que essa alteração causa acontece de forma assíncrona.

A renderização visual do DOM é assíncrona no Chrome e não acontecerá até que a pilha de funções JavaScript atual tenha sido limpa e o navegador esteja livre para aceitar um novo evento. Outros navegadores podem usar threads separados para manipular o código JavaScript e a renderização do navegador, ou podem permitir que alguns eventos tenham prioridade enquanto um alerta está interrompendo a execução de outro evento.

Você pode ver isso de duas maneiras:

  1. Se você adicionar for(var i=0; i<1000000; i++) { }antes do alerta, deu ao navegador bastante tempo para fazer um redesenho, mas não deu, porque a pilha de funções não foi apagada ( addainda está em execução).

  2. Se você atrasar o seu alertpor meio de um assíncrono setTimeout(function() { alert('random'); }, 1), o processo de redesenho irá adiante da função atrasada por setTimeout.

    • Isso não funciona se você usar um tempo limite de 0, possivelmente porque o Chrome dá prioridade à fila de eventos para 0tempos limite antes de quaisquer outros eventos (ou pelo menos antes dos eventos de redesenho).
Apsillers
fonte
Obrigado por responder! Por favor, não que nem setTimeout(func, 1)funcionou todas as vezes, confira este vídeo: youtu.be/r8caVE_a5KQ
apieceofbart