web-dev-qa-db-fra.com

charger et exécuter l'ordre des scripts

Il y a tellement de façons d'inclure JavaScript dans une page HTML. Je connais les options suivantes:

  • code en ligne ou chargé depuis l'URI externe
  • inclus dans la balise <head> ou <body> [ 1 , 2 ]
  • n'ayant aucun attribut, defer ou async (uniquement les scripts externes)
  • inclus dans une source statique ou ajouté dynamiquement par d'autres scripts (à différents états d'analyse, avec différentes méthodes)

Sans compter les scripts de navigateur du disque dur, javascript: les URI et les attributs onEvent- [], il existe déjà 16 alternatives pour faire exécuter JS et je suis sûr d'avoir oublié quelque chose.

Le chargement rapide (parallèle) ne me préoccupe pas tellement, mais l'ordre d'exécution (qui peut dépendre de l'ordre de chargement et ordre des documents ) m'intéresse davantage. Existe-t-il une bonne référence (multi-navigateurs) couvrant réellement tous les cas? Par exemple http://www.websiteoptimization.com/speed/Tweak/defer/ ne traite que 6 d'entre eux et teste principalement des navigateurs anciens.

Comme je crains qu'il n'y en ait pas, voici ma question précise: j'ai quelques scripts de tête (externes) pour l'initialisation et le chargement de script. Ensuite, j'ai deux scripts statiques en ligne à la fin du corps. La première permet au chargeur de script d'ajouter dynamiquement un autre élément de script (référençant js externe) au corps. Le second des scripts en ligne statiques veut utiliser js à partir du script externe ajouté. Peut-il compter que l'autre a été exécuté (et pourquoi :-)?

237
Bergi

Si vous ne chargez pas les scripts de manière dynamique ni ne les marquez comme suit: defer ou async, les scripts sont chargés dans l'ordre rencontré dans la page. Peu importe qu'il s'agisse d'un script externe ou d'un script en ligne, ils sont exécutés dans l'ordre dans lequel ils sont rencontrés dans la page. Les scripts en ligne qui suivent les scripts externes sont conservés jusqu'à ce que tous les scripts externes les précédant soient chargés et exécutés.

Les scripts asynchrones (quelle que soit la manière dont ils sont spécifiés comme asynchrones) se chargent et s'exécutent dans un ordre imprévisible. Le navigateur les charge en parallèle et il est libre de les exécuter dans l'ordre de leur choix.

Il n'y a pas d'ordre prévisible entre plusieurs choses asynchrones. Si l'on avait besoin d'un ordre prévisible, il faudrait alors le coder en enregistrant les notifications de chargement à partir des scripts asynchrones et en séquençant manuellement les appels javascript lorsque les éléments appropriés sont chargés.

Quand une balise de script est insérée dynamiquement, le comportement de l'ordre d'exécution dépend du navigateur. Vous pouvez voir comment Firefox se comporte dans cet article de référence . En résumé, les nouvelles versions de Firefox utilisent par défaut une balise de script ajoutée dynamiquement à asynchrone, sauf si la balise de script a été définie autrement.

Une balise de script avec async peut être exécutée dès son chargement. En fait, le navigateur peut mettre l'analyseur en pause de quoi que ce soit d'autre et exécuter ce script. Donc, il peut vraiment fonctionner à tout moment. Si le script était mis en cache, il pourrait s'exécuter presque immédiatement. Si le script prend un certain temps à charger, il peut s'exécuter une fois l'analyseur terminé. La seule chose à retenir avec async est qu’elle peut être exécutée à tout moment et que cette heure n’est pas prévisible.

Une balise de script avec defer attend que l'analyse complète soit terminée, puis exécute tous les scripts marqués avec defer dans l'ordre dans lequel ils ont été rencontrés. Cela vous permet de marquer plusieurs scripts qui dépendent les uns des autres en tant que defer. Ils seront tous reportés jusqu'à ce que l'analyseur de document soit terminé, mais ils s'exécuteront dans l'ordre dans lequel ils ont été rencontrés en préservant leurs dépendances. Je pense à defer comme si les scripts étaient déposés dans une file d’attente qui sera traitée une fois l’analyseur terminé. Techniquement, le navigateur peut télécharger les scripts en arrière-plan à tout moment, mais ils n'exécutent ni ne bloquent l'analyseur qu'après avoir analysé la page, analysé et exécuté les scripts en ligne non marqués defer ou async.

Voici une citation de cet article:

les scripts insérés dans un script s'exécutent de manière asynchrone dans IE et WebKit, mais de manière synchrone dans Opera et dans les versions antérieures à Firefox Firefox.

La partie pertinente de la spécification HTML5 (pour les nouveaux navigateurs compatibles) est ici . Il y a beaucoup de choses écrites là-dessus sur le comportement asynchrone. De toute évidence, cette spécification ne s’applique pas aux navigateurs plus anciens (ou aux navigateurs mal confirmés) dont vous devrez probablement tester le comportement pour déterminer le comportement.

Une citation de la spécification HTML5:

Ensuite, la première des options suivantes décrivant la situation doit être suivie:

Si l'élément a un attribut src et que l'élément a un attribut différé et que l'élément a été marqué comme "parser-inséré", et que l'élément ne possède pas d'attribut asynchrone L'élément doit être ajouté à la fin de la liste des scripts qui seront exécutés lorsque l'analyse du document sera terminée, associée au document de l'analyseur qui a créé l'élément.

La tâche que la source de tâches réseau place dans la file d'attente des tâches une fois l'algorithme d'extraction terminé doit définir l'indicateur "prêt à être exécuté par l'analyseur" de l'élément. L'analyseur gérera l'exécution du script.

Si l'élément a un attribut src et que l'élément a été marqué comme "parseur-inséré", et que l'élément ne possède pas d'attribut asynchrone L'élément est le script d'analyse syntaxique en attente du document de l'analyseur qui a créé l'élément. (Il ne peut y avoir qu'un seul script de ce type par document à la fois.)

La tâche que la source de tâches réseau place dans la file d'attente des tâches une fois l'algorithme d'extraction terminé doit définir l'indicateur "prêt à être exécuté par l'analyseur" de l'élément. L'analyseur gérera l'exécution du script.

Si l'élément ne possède pas d'attribut src et que l'élément a été marqué en tant que "parseur-inséré", et que le document de l'analyseur HTML ou de l'analyseur XML qui a créé l'élément de script a un style sheet qui bloque les scripts L'élément est le script d'analyse syntaxique en attente du document de l'analyseur qui a créé l'élément. (Il ne peut y avoir qu'un seul script de ce type par document à la fois.)

Définit l'indicateur "prêt à être exécuté par l'analyseur". L'analyseur gérera l'exécution du script.

Si l'élément possède un attribut src, ne possède pas d'attribut asynchrone et ne possède pas l'indicateur "force-async" L'élément doit être ajouté jusqu’à la fin de la liste des scripts à exécuter dans l’ordre le plus tôt possible associés au document de l’élément script au moment de la préparation de l’algorithme de script.

La tâche que la source de tâches réseau place dans la file d'attente des tâches une fois l'algorithme de récupération terminé doit exécuter les étapes suivantes:

Si l'élément n'est pas maintenant le premier élément de la liste des scripts à exécuter dans l'ordre le plus tôt possible auquel il a été ajouté ci-dessus, , puis marquez L'élément est prêt, mais annulez ces étapes sans exécuter le script pour le moment.

Exécution: Exécutez le bloc de script correspondant au premier élément de script de cette liste de scripts à exécuter dans l’ordre le plus rapidement possible.

Supprimez le premier élément de cette liste de scripts à exécuter dans l’ordre le plus rapidement possible.

Si cette liste de scripts qui s'exécutent dans l'ordre le plus tôt possible n'est toujours pas vide et que la première entrée a déjà été marquée comme étant prête, passez à l'étape étiquetée exécution.

Si l'élément a un attribut src L'élément doit être ajouté à l'ensemble des scripts à exécuter dans les meilleurs délais du document de l'élément de script à l'heure de la préparation d'un algorithme de script.

La tâche que la source de tâches réseau place dans la file de tâches une fois l'algorithme d'extraction terminé doit exécuter le bloc de script, puis supprimer l'élément de l'ensemble de scripts à exécuter le plus rapidement possible.

Sinon L'agent utilisateur doit immédiatement exécuter le bloc de script, même si d'autres scripts sont déjà en cours d'exécution.


Qu'en est-il des scripts de module Javascript, type="module"?

Javascript prend maintenant en charge le chargement de module avec une syntaxe comme celle-ci:

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

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

Ou, avec l'attribut src:

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

Tous les scripts avec type="module" reçoivent automatiquement l'attribut defer. Cela les télécharge en parallèle (sinon en ligne) avec les autres chargements de la page, puis les lance dans l’ordre, mais une fois l’analyseur terminé.

Les scripts de module peuvent également recevoir l'attribut async qui les exécutera dès que possible, sans attendre que l'analyseur soit terminé ni d'exécuter le script async dans un ordre particulier par rapport aux autres scripts.

Il y a un tableau chronologique très utile qui montre l'extraction et l'exécution de différentes combinaisons de scripts, y compris les scripts de module, ici dans cet article: Chargement du module Javascript .

293
jfriend00

Le navigateur exécute les scripts dans l'ordre dans lequel il les trouve. Si vous appelez un script externe, la page sera bloquée jusqu'à ce que le script soit chargé et exécuté.

Pour tester ce fait:

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

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

Les scripts ajoutés dynamiquement sont exécutés dès qu'ils sont ajoutés au document.

Pour tester ce fait:

<!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>

L'ordre des alertes est "ajouté" -> "bonjour!" -> "finale"

Si, dans un script, vous essayez d'accéder à un élément qui n'a pas encore été atteint (exemple: <script>do something with #blah</script><div id="blah"></div>), vous obtiendrez une erreur.

Dans l’ensemble, oui, vous pouvez inclure des scripts externes, puis accéder à leurs fonctions et variables, mais uniquement si vous quittez la balise actuelle <script> et en démarrez une nouvelle.

11
Niet the Dark Absol

Un excellent résumé par @ addyosmani

enter image description here

Sans vergogne copié de https://addyosmani.com/blog/script-priorities/

7
Dehan de Croos

Après avoir testé de nombreuses options, j'ai constaté que la solution simple suivante charge les scripts chargés dynamiquement dans l'ordre dans lequel ils ont été ajoutés dans tous les navigateurs modernes.

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'])
1
Flion