charger et exécuter l'ordre des scripts


265

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

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

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

Je ne suis pas tellement préoccupé par le chargement rapide (parallèle), je suis plus curieux de l'ordre d'exécution (qui peut dépendre de l'ordre de chargement et de l' ordre des documents ). Existe-t-il une bonne référence (cross-browser) qui couvre vraiment tous les cas? Par exemple, http://www.websiteoptimization.com/speed/tweak/defer/ ne traite que de 6 d'entre eux et teste principalement les anciens navigateurs.

Comme je crains qu'il n'y en ait pas, voici ma question spécifique: 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. Le premier permet au chargeur de script d'ajouter dynamiquement un autre élément de script (référençant des js externes) au corps. Le deuxième des scripts statiques en ligne veut utiliser js à partir du script externe ajouté. Peut-il compter sur l'exécution de l'autre (et pourquoi :-)?


Avez-vous regardé Chargement de scripts sans blocage par Steve Souders? Il est un peu daté maintenant, mais contient toujours de précieuses informations sur le comportement du navigateur compte tenu d'une technique de chargement de script spécifique.
Josh Habdas

Réponses:


331

Si vous ne chargez pas dynamiquement des scripts ou ne les marquez pas comme deferou 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 où ils sont rencontrés dans la page. Les scripts en ligne qui viennent après les scripts externes sont conservés jusqu'à ce que tous les scripts externes qui les ont précédés soient chargés et exécutés.

Les scripts asynchrones (quelle que soit la façon 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 son 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 le coder en s'enregistrant pour 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.

Lorsqu'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 versions les plus récentes de Firefox utilisent par défaut une balise de script ajoutée dynamiquement à async sauf si la balise de script a été définie autrement.

Une balise de script avec asyncpeut être exécutée dès qu'elle est chargée. En fait, le navigateur peut suspendre l'analyseur de tout ce qu'il faisait et exécuter ce script. Donc, il peut vraiment fonctionner à presque n'importe quel moment. Si le script a été mis en cache, il peut 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 asyncest qu'il peut s'exécuter à tout moment et que l'heure n'est pas prévisible.

Une balise de script deferattend jusqu'à ce que l'analyseur complet soit terminé, puis exécute tous les scripts marqués deferdans l'ordre dans lequel ils ont été rencontrés. Cela vous permet de marquer plusieurs scripts qui dépendent les uns des autres comme defer. Ils seront tous reportés jusqu'à ce que l'analyseur de document soit terminé, mais ils s'exécuteront dans l'ordre où ils ont été rencontrés, tout en préservant leurs dépendances. Je pense deferque les scripts sont 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écuteront ni ne bloqueront l'analyseur tant que l'analyseur n'aura pas terminé l'analyse de la page et l'analyse et l'exécution de scripts en ligne non marqués deferou async.

Voici une citation de cet article:

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

La partie pertinente de la spécification HTML5 (pour les navigateurs compatibles plus récents) est ici . Il y a beaucoup écrit là-dessus sur le comportement asynchrone. Évidemment, cette spécification ne s'applique pas aux navigateurs plus anciens (ou aux navigateurs mal conformes) dont vous auriez probablement à tester le comportement pour déterminer.

Une citation de la spécification HTML5:

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

Si l'élément a un attribut src, et l'élément a un attribut defer, et l'élément a été marqué comme "inséré par l'analyseur", et l'élément n'a pas d'attribut async. L'élément doit être ajouté à la fin de la liste de les scripts qui s'exécuteront une fois l'analyse du document terminée associée au document de l'analyseur qui a créé l'élément.

La tâche que la source de tâches de mise en 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 "inséré par l'analyseur" et que l'élément n'a pas d'attribut asynchrone L'élément est le script de blocage de l'analyse 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 de mise en 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 n'a pas d'attribut src et que l'élément a été marqué comme "inséré par l'analyseur" et que le document de l'analyseur HTML ou de l'analyseur XML qui a créé l'élément de script a une feuille de style qui bloque les scripts L'élément est le script de blocage-analyse 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éfinissez 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, n'a pas d'attribut async et n'a pas le flag "force-async" défini L'élément doit être ajouté à la fin de la liste des scripts qui s'exécuteront dans l'ordre dès que possible associés avec le document de l'élément de script au moment de la préparation d'un algorithme de script.

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

Si l'élément n'est pas maintenant le premier élément de la liste des scripts qui s'exécutera dans l'ordre dès que possible auquel il a été ajouté ci-dessus, marquez l'élément comme prêt mais abandonnez 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 qui s'exécutera dans l'ordre dès que possible.

Supprimez le premier élément de cette liste de scripts qui s'exécutera dans l'ordre dès que possible.

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

Si l'élément a un attribut src L'élément doit être ajouté à l'ensemble de scripts qui s'exécutera dès que possible du document de l'élément de script au moment du démarrage de la préparation d'un algorithme de script.

La tâche que la source de tâches de mise en réseau place dans la file d'attente des 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 qui s'exécutera dès que 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 désormais en charge le chargement de modules avec une syntaxe comme celle-ci:

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

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

Ou, avec srcattribut:

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

Tous les scripts avec type="module"reçoivent automatiquement l' deferattribut. Cela les télécharge en parallèle (sinon en ligne) avec un autre chargement de la page, puis les exécute dans l'ordre, mais une fois l'analyseur terminé.

Les scripts de module peuvent également recevoir l' asyncattribut qui exécutera les scripts de module en ligne dès que possible, sans attendre la fin de l'analyseur et sans attendre d'exécuter le asyncscript dans un ordre particulier par rapport aux autres scripts.

Il y a un graphique chronologique assez utile qui montre la récupération et l'exécution de différentes combinaisons de scripts, y compris les scripts de module ici dans cet article: Chargement du module Javascript .


Merci pour la réponse, mais le problème est que le script est ajouté dynamiquement à la page, ce qui signifie qu'il est considéré comme asynchrone . Ou cela ne fonctionne-t-il que dans <head>? Et mon expérience est aussi qu'ils sont exécutés dans l'ordre des documents?
Bergi

@Bergi - S'il est ajouté dynamiquement, il est alors asynchrone et l'ordre d'exécution est indéterminé, sauf si vous écrivez du code pour le contrôler.
jfriend00

Juste, Kolink déclare le contraire ...
Bergi

@Bergi - OK, j'ai modifié ma réponse pour dire que les scripts asynchrones se chargent dans un ordre indéterminé. Ils peuvent être chargés dans n'importe quel ordre. Si j'étais vous, je ne compterais pas sur l'observation de Kolink comme c'est toujours le cas. Je ne connais aucune norme qui dit qu'un script ajouté dynamiquement doit être exécuté immédiatement et doit bloquer les autres scripts jusqu'à ce qu'il soit chargé. Je m'attendrais à ce que cela dépende du navigateur et aussi peut-être dépendant de facteurs environnementaux (que le script soit mis en cache, etc.).
jfriend00

1
@RuudLenders - Cela dépend de l'implémentation du navigateur. La rencontre de la balise de script plus tôt dans le document, mais marquée par deferdonne à l'analyseur la possibilité de commencer son téléchargement plus tôt tout en différant son exécution. Notez que si vous avez beaucoup de scripts du même hôte, alors le démarrage du téléchargement plus tôt peut en fait ralentir le téléchargement des autres depuis le même hôte (car ils rivalisent pour la bande passante) que votre page attend (qui ne le sont pas defer) donc cela pourrait être une épée à double tranchant.
jfriend00

13

Le navigateur exécutera les scripts dans l'ordre où ils les trouveront. Si vous appelez un script externe, il bloquera la page 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!" -> "final"

Si dans un script vous tentez 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 <script>balise actuelle et en commencez une nouvelle.


Je peux confirmer ce comportement. Mais il y a des indices sur nos pages de commentaires, cela pourrait ne fonctionner que lorsque test.php est mis en cache. Connaissez-vous des liens de spécification / référence à ce sujet?
Bergi

4
link.js ne bloque pas. Utilisez un script similaire à votre php pour simuler un long temps de téléchargement.
1983

14
Cette réponse est incorrecte. Il n'est pas toujours vrai que "les scripts ajoutés dynamiquement sont exécutés dès qu'ils sont ajoutés au document". Parfois, cela est vrai (par exemple pour les anciennes versions de Firefox), mais ce n'est généralement pas le cas. L'ordre d'exécution, comme mentionné dans la réponse de jfriend00, n'est pas déterminé.
Fabio Beltramini

1
Cela n'a aucun sens que les scripts soient exécutés dans l'ordre où ils apparaissent sur la page, qu'ils soient en ligne ou non. Pourquoi alors l'extrait de gestionnaire de balises Google et bien d'autres que j'ai vus auraient-ils du code pour insérer un nouveau script au-dessus de toutes les autres balises de script dans la page? Cela n'aurait aucun sens si les scripts ci-dessus ont déjà été chargés sûrement ?? ou est-ce que je manque quelque chose.
user3094826


2

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 sont 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'])
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.