carregar e executar a ordem dos scripts

265

Existem muitas maneiras diferentes de incluir JavaScript em uma página html. Conheço as seguintes opções:

  • código embutido ou carregado de URI externo
  • incluído na tag <head> ou <body> [ 1 , 2 ]
  • sem nenhum deferou asyncatributo (somente scripts externos)
  • incluído na fonte estática ou adicionado dinamicamente por outros scripts (em diferentes estados de análise, com métodos diferentes)

Sem contar os onEventscripts de navegador do disco rígido, javascript: URIs e -atributes [ 3 ], já existem 16 alternativas para executar o JS e tenho certeza de que esqueci alguma coisa.

Não estou tão preocupado com o carregamento rápido (paralelo), estou mais curioso sobre a ordem de execução (que pode depender da ordem de carregamento e da ordem dos documentos ). Existe uma boa referência (entre navegadores) que cubra realmente todos os casos? Por exemplo, http://www.websiteoptimization.com/speed/tweak/defer/ lida apenas com 6 deles e testa principalmente navegadores antigos.

Como eu temo que não exista, aqui está minha pergunta específica: Eu tenho alguns scripts de cabeçalho (externos) para inicialização e carregamento de scripts. Então, eu tenho dois scripts estáticos e embutidos no final do corpo. O primeiro permite que o carregador de scripts inclua dinamicamente outro elemento de script (referenciando js externos) ao corpo. O segundo dos scripts estáticos inline deseja usar js do script externo adicionado. Ele pode confiar na execução do outro (e por que :-)?

Bergi
fonte
Você já viu Loading Scripts Without Blocking de Steve Souders? É um pouco datado agora, mas ainda contém algumas informações valiosas sobre o comportamento do navegador, dada uma técnica específica de carregamento de script.
Josh Habdas 15/03

Respostas:

331

Se você não estiver carregando dinamicamente scripts ou marcando-os como deferou async, os scripts serão carregados na ordem encontrada na página. Não importa se é um script externo ou um script embutido - eles são executados na ordem em que são encontrados na página. Os scripts embutidos que vêm depois dos scripts externos são mantidos até que todos os scripts externos que vieram antes deles sejam carregados e executados.

Os scripts assíncronos (independentemente de como eles são especificados como assíncronos) são carregados e executados em uma ordem imprevisível. O navegador os carrega em paralelo e é livre para executá-los na ordem que desejar.

Não há ordem previsível entre várias coisas assíncronas. Se alguém precisasse de um pedido previsível, teria que ser codificado registrando-se para receber notificações de carregamento dos scripts assíncronos e sequenciando manualmente as chamadas javascript quando as coisas apropriadas forem carregadas.

Quando uma tag de script é inserida dinamicamente, o comportamento da ordem de execução depende do navegador. Você pode ver como o Firefox se comporta neste artigo de referência . Em poucas palavras, as versões mais recentes do Firefox padronizam uma tag de script adicionada dinamicamente para assíncrona, a menos que a tag de script tenha sido definida de outra forma.

Uma tag de script com asyncpode ser executada assim que for carregada. De fato, o navegador pode pausar o analisador do que quer que esteja fazendo e executar esse script. Portanto, ele realmente pode ser executado a qualquer momento. Se o script foi armazenado em cache, ele pode ser executado quase imediatamente. Se o script demorar um pouco para carregar, ele poderá ser executado após a conclusão do analisador. A única coisa a lembrar asyncé que ele pode ser executado a qualquer momento e esse tempo não é previsível.

Uma tag de script deferaguarda até que o analisador inteiro seja concluído e, em seguida, executa todos os scripts marcados deferna ordem em que foram encontrados. Isso permite que você marque vários scripts que dependem um do outro como defer. Todos serão adiados até após a conclusão do analisador de documentos, mas serão executados na ordem em que foram encontrados, preservando suas dependências. Penso que deferos scripts são descartados em uma fila que será processada após a conclusão do analisador. Tecnicamente, o navegador pode estar baixando os scripts em segundo plano a qualquer momento, mas não executará ou bloqueará o analisador até que o analisador termine de analisar a página e de executar e executar scripts embutidos que não estejam marcados deferou async.

Aqui está uma citação desse artigo:

scripts inseridos em script são executados de forma assíncrona no IE e WebKit, mas de forma síncrona no Opera e no Firefox anterior à 4.0.

A parte relevante da especificação HTML5 (para navegadores compatíveis mais recentes) está aqui . Há muita coisa escrita sobre comportamento assíncrono. Obviamente, essa especificação não se aplica a navegadores mais antigos (ou mal-conformes), cujo comportamento você provavelmente precisaria testar para determinar.

Uma citação da especificação HTML5:

Em seguida, a primeira das seguintes opções que descreve a situação deve ser seguida:

Se o elemento tiver um atributo src e o elemento tiver um atributo defer, e o elemento tiver sido marcado como "inserido pelo analisador", e o elemento não tiver um atributo assíncrono. O elemento deve ser adicionado ao final da lista de scripts que serão executados quando o documento terminar de analisar associado ao documento do analisador que criou o elemento.

A tarefa que a origem da tarefa de rede coloca na fila de tarefas após a conclusão do algoritmo de busca deve definir o sinalizador "pronto para ser executado pelo analisador" do elemento. O analisador manipulará a execução do script.

Se o elemento tiver um atributo src e o elemento tiver sido marcado como "inserido pelo analisador" e o elemento não tiver um atributo assíncrono O elemento é o script de bloqueio de análise pendente do Documento do analisador que criou o elemento. (Só pode haver um desses scripts por documento de cada vez.)

A tarefa que a origem da tarefa de rede coloca na fila de tarefas após a conclusão do algoritmo de busca deve definir o sinalizador "pronto para ser executado pelo analisador" do elemento. O analisador manipulará a execução do script.

Se o elemento não tiver um atributo src e o elemento tiver sido sinalizado como "inserido pelo analisador", e o Documento do analisador HTML ou XML que criou o elemento de script terá uma folha de estilos que está bloqueando os scripts. O elemento é o script de bloqueio de análise pendente do Documento do analisador que criou o elemento. (Só pode haver um desses scripts por documento de cada vez.)

Defina o sinalizador "pronto para ser executado pelo analisador" do elemento. O analisador manipulará a execução do script.

Se o elemento tiver um atributo src, não possuir um atributo assíncrono e não tiver o sinalizador "force-async" definido, o elemento deverá ser adicionado ao final da lista de scripts que serão executados na ordem o mais breve possível associada com o documento do elemento de script no momento em que o algoritmo de preparação de um script foi iniciado.

A tarefa que a origem da tarefa de rede coloca na fila de tarefas após a conclusão do algoritmo de busca deve executar as seguintes etapas:

Se agora o elemento não for o primeiro na lista de scripts que serão executados na ordem o mais rápido possível à qual foi adicionado acima, marque o elemento como pronto, mas aborte essas etapas sem executar o script ainda.

Execução: Execute o bloco de scripts correspondente ao primeiro elemento de script nesta lista de scripts que serão executados na ordem o mais rápido possível.

Remova o primeiro elemento desta lista de scripts que serão executados em ordem o mais rápido possível.

Se essa lista de scripts que serão executados em ordem o mais rápido possível ainda não estiver vazia e a primeira entrada já tiver sido marcada como pronta, volte para a etapa rotulada execução.

Se o elemento tiver um atributo src O elemento deve ser incluído no conjunto de scripts que serão executados o mais rápido possível do Documento do elemento de script no momento em que o algoritmo de preparação de um script foi iniciado.

A tarefa que a origem da tarefa de rede coloca na fila de tarefas após a conclusão do algoritmo de busca deve executar o bloco de scripts e remover o elemento do conjunto de scripts que serão executados o mais rápido possível.

Caso contrário, o agente do usuário deve executar imediatamente o bloco de scripts, mesmo se outros scripts já estiverem em execução.


E os scripts do módulo Javascript type="module"?

Javascript agora tem suporte para carregamento de módulos com sintaxe como esta:

<script type="module">
  import {addTextToBody} from './utils.mjs';

  addTextToBody('Modules are pretty cool.');
</script>

Ou, com o srcatributo:

<script type="module" src="http://somedomain.com/somescript.mjs">
</script>

Todos os scripts com type="module"recebem automaticamente o deferatributo Isso os baixa em paralelo (se não estiver em linha) com outro carregamento da página e os executa em ordem, mas após a conclusão do analisador.

Os scripts do módulo também podem receber o asyncatributo que executará os scripts do módulo embutido o mais rápido possível, sem esperar até que o analisador seja concluído e sem esperar para executar o asyncscript em qualquer ordem específica em relação a outros scripts.

Há um gráfico de linha do tempo bastante útil que mostra a busca e a execução de diferentes combinações de scripts, incluindo scripts de módulo aqui neste artigo: Carregamento de módulo Javascript .

jfriend00
fonte
Obrigado pela resposta, mas o problema é que o script é adicionado dinamicamente à página, o que significa que é considerado assíncrono . Ou isso funciona apenas em <head>? E minha experiência também é que eles são executados em ordem de documentos?
Bergi
@ Bergi - Se for adicionado dinamicamente, será assíncrono e a ordem de execução é indeterminada, a menos que você escreva um código para controlá-lo.
usar o seguinte comando
Assim, Kolink afirma o contrário ...
Bergi
@ Bergi - OK, eu modifiquei minha resposta para dizer que os scripts assíncronos carregam em uma ordem indeterminada. Eles podem ser carregados em qualquer ordem. Se eu fosse você, não contaria com a observação de Kolink como sempre é. Não conheço nenhum padrão que diga que um script adicionado dinamicamente precisa ser executado imediatamente e que impeça a execução de outros scripts até que seja carregado. Eu esperaria que isso dependesse do navegador e também talvez dependesse de fatores ambientais (se o script está armazenado em cache, etc ...).
usar o seguinte comando
1
@RuudLenders - Isso depende da implementação do navegador. O encontro da tag de script no início do documento, mas marcado com, deferoferece ao analisador a oportunidade de iniciar o download mais cedo, enquanto adia a execução. Observe que, se você tiver muitos scripts do mesmo host, iniciar o download mais cedo pode realmente atrasar o download de outros usuários do mesmo host (pois eles competem pela largura de banda) em que sua página está aguardando (que não são defer). isso poderia ser uma faca de dois gumes.
jfriend00
13

O navegador executará os scripts na ordem em que os encontrar. Se você chamar um script externo, ele bloqueará a página até que o script seja carregado e executado.

Para testar esse fato:

// file: test.php
sleep(10);
die("alert('Done!');");

// HTML file:
<script type="text/javascript" src="test.php"></script>

Os scripts adicionados dinamicamente são executados assim que são anexados ao documento.

Para testar esse fato:

<!DOCTYPE HTML>
<html>
<head>
    <title>Test</title>
</head>
<body>
    <script type="text/javascript">
        var s = document.createElement('script');
        s.type = "text/javascript";
        s.src = "link.js"; // file contains alert("hello!");
        document.body.appendChild(s);
        alert("appended");
    </script>
    <script type="text/javascript">
        alert("final");
    </script>
</body>
</html>

A ordem dos alertas é "anexada" -> "olá!" -> "final"

Se em um script você tentar acessar um elemento que ainda não foi alcançado (exemplo <script>do something with #blah</script><div id="blah"></div>:), você receberá um erro.

No geral, sim, você pode incluir scripts externos e acessar suas funções e variáveis, mas somente se você sair da <script>tag atual e iniciar uma nova.

Niet, o Escuro Absol
fonte
Eu posso confirmar esse comportamento. Mas há dicas em nossas páginas de feedback, de que ele só funcionará quando o test.php estiver em cache. Você conhece algum link de especificação / referência sobre isso?
Bergi
4
link.js não está bloqueando. Use um script semelhante ao seu php one para simular um longo tempo de download.
1983
14
Esta resposta está incorreta. Não é sempre o caso em que "scripts adicionados dinamicamente são executados assim que são anexados ao documento". Às vezes isso é verdade (por exemplo, para versões antigas do Firefox), mas geralmente não é. A ordem de execução, conforme mencionado na resposta de jfriend00, não é determinada.
Fabio Beltramini
1
Não faz sentido que os scripts sejam executados na ordem em que aparecem na página, independentemente de estarem inline ou não. Por que, então, o snippet do gerenciador de tags do Google e muitos outros que eu vi possuem código para inserir um novo script acima de todas as outras tags de script na página? Não faria sentido fazer isso, se os scripts acima já tiverem sido carregados com certeza? Ou eu estou esquecendo de alguma coisa.
user3094826
2

Depois de testar muitas opções, descobri que a solução simples a seguir está carregando os scripts carregados dinamicamente na ordem em que são adicionados em todos os navegadores modernos

loadScripts(sources) {
    sources.forEach(src => {
        var script = document.createElement('script');
        script.src = src;
        script.async = false; //<-- the important part
        document.body.appendChild( script ); //<-- make sure to append to body instead of head 
    });
}

loadScripts(['/scr/script1.js','src/script2.js'])
Flion
fonte