Puis-je exécuter javascript avant que la page entière ne soit chargée?

Réponses:

186

Non seulement vous le pouvez , mais vous devez faire un effort particulier pour ne pas le faire si vous ne le souhaitez pas. :-)

Lorsque le navigateur rencontre une scriptbalise classique lors de l'analyse du HTML, il arrête l'analyse et passe la main à l'interpréteur JavaScript, qui exécute le script. L'analyseur ne continue pas tant que l'exécution du script n'est pas terminée (car le script peut effectuer des document.writeappels au balisage de sortie que l'analyseur doit gérer).

C'est le comportement par défaut, mais vous avez quelques options pour retarder l'exécution du script:

  1. Utilisez des modules JavaScript. Un type="module"script est différé jusqu'à ce que le HTML ait été entièrement analysé et le DOM initial créé. Ce n'est pas la principale raison d'utiliser des modules, mais c'est l'une des raisons:

    <script type="module" src="./my-code.js"></script>
    <!-- Or -->
    <script type="module">
    // Your code here
    </script>

    Le code sera récupéré (s'il est séparé) et analysé en parallèle avec l'analyse HTML, mais ne sera pas exécuté tant que l'analyse HTML n'est pas terminée. (Si le code de votre module est en ligne plutôt que dans son propre fichier, il est également différé jusqu'à ce que l'analyse HTML soit terminée.)

    Cela n'était pas disponible lorsque j'ai écrit cette réponse pour la première fois en 2010, mais ici en 2020, tous les principaux navigateurs modernes prennent en charge les modules de manière native, et si vous avez besoin de prendre en charge des navigateurs plus anciens, vous pouvez utiliser des bundles comme Webpack et Rollup.js.

  2. Utilisez l' deferattribut sur une balise de script classique:

    <script defer src="./my-code.js"></script>

    Comme pour le module, le code dans my-code.jssera récupéré et analysé en parallèle avec l'analyse HTML, mais ne sera pas exécuté tant que l'analyse HTML n'est pas terminée. Mais , deferne fonctionne pas avec le contenu de script en ligne, uniquement avec des fichiers externes référencés via src.

  3. Je ne pense pas que ce soit ce que vous voulez, mais vous pouvez utiliser l' asyncattribut pour indiquer au navigateur de récupérer le code JavaScript en parallèle avec l'analyse HTML, mais l'exécuter dès que possible, même si l'analyse HTML n'est pas terminée . Vous pouvez le mettre sur un type="module"tag ou l'utiliser à la place d' deferun scripttag classique .

  4. Placez la scriptbalise à la fin du document, juste avant la </body>balise de fermeture :

    <!doctype html>
    <html>
    <!-- ... -->
    <body>
    <!-- The document's HTML goes here -->
    <script type="module" src="./my-code.js"></script><!-- Or inline script -->
    </body>
    </html>

    De cette façon, même si le code est exécuté dès sa rencontre, tous les éléments définis par le HTML ci-dessus existent et sont prêts à être utilisés.

    Auparavant, cela provoquait un retard supplémentaire sur certains navigateurs car ils ne commenceraient pas à récupérer le code tant que la scriptbalise n'était pas rencontrée, mais les navigateurs modernes analysent à l'avance et commencent la prélecture. Pourtant, c'est vraiment le troisième choix à ce stade, les deux modules et defersont de meilleures options.

La spécification a un diagramme utile montrant une première scriptétiquette, defer, async, type="module"et type="module" asyncet le moment où le code JavaScript est extrait et exécutez:

entrez la description de l'image ici

Voici un exemple du comportement par défaut, une scriptbalise brute :

.found {
    color: green;
}
<p>Paragraph 1</p>
<script>
    if (typeof NodeList !== "undefined" && !NodeList.prototype.forEach) {
        NodeList.prototype.forEach = Array.prototype.forEach;
    }
    document.querySelectorAll("p").forEach(p => {
        p.classList.add("found");
    });
</script>
<p>Paragraph 2</p>

(Voir ma réponse ici pour plus de détails sur ce NodeListcode.)

Lorsque vous exécutez cela, vous voyez «Paragraphe 1» en vert, mais «Paragraphe 2» est noir, car le script s'est exécuté de manière synchrone avec l'analyse HTML et n'a donc trouvé que le premier paragraphe, pas le second.

En revanche, voici un type="module"script:

.found {
    color: green;
}
<p>Paragraph 1</p>
<script type="module">
    document.querySelectorAll("p").forEach(p => {
        p.classList.add("found");
    });
</script>
<p>Paragraph 2</p>

Remarquez comment ils sont tous les deux verts maintenant; le code n'a pas été exécuté tant que l'analyse HTML n'était pas terminée. Ce serait également vrai avec un defer scriptcontenu externe (mais pas un contenu en ligne).

(Il n'y avait pas besoin pour le NodeListcontrôle là parce que tous les modules supportant navigateur moderne a déjà forEachsur NodeList.)

Dans ce monde moderne, il n'y a aucune valeur réelle à l' DOMContentLoadedévénement de la fonctionnalité «prête» que PrototypeJS, jQuery, ExtJS, Dojo et la plupart des autres ont fourni à l'époque (et fournissent toujours); utilisez simplement des modules ou defer. Même à l'époque, il n'y avait pas beaucoup de raisons de les utiliser (et ils étaient souvent utilisés de manière incorrecte, bloquant la présentation de la page alors que toute la bibliothèque jQuery était chargée parce que le scriptétait dans le document headplutôt qu'après), ce que certains développeurs à Google a signalé très tôt. Cela faisait également partie de la raison pour laquelle la recommandation de YUI était de mettre des scripts à la fin du body, encore une fois dans la journée.

TJ Crowder
la source
1
@FeRD - Merci pour la modification. Les critiques l'ont rejeté, mais vous aviez tout à fait raison. :-)
TJ Crowder
Ou mettez tout cela dans une fonction et mettez-le <body onload="doIt()>".
Justin Liu
@JustinLiu - L' loadévénement se produit très tard dans le cycle de chargement de la page, il n'est presque jamais préférable d'attendre avant d'exécuter votre JavaScript. Mais oui, c'est une option. J'ai révisé la réponse pour 2020, d'ailleurs.
TJ Crowder le
Ou vous pouvez utiliserdocument.addEventListener('DOMContentLoaded', function(){ //doyour stuff })
Justin Liu
@JustinLiu - Ouais, voir le dernier paragraphe. :-) Je n'en ai jamais vu le besoin, mais vous pouvez.
TJ Crowder le
6

Vous pouvez exécuter du code javascript à tout moment. AFAIK il est exécuté au moment où le navigateur atteint la balise <script> où il se trouve. Mais vous ne pouvez pas accéder aux éléments qui ne sont pas encore chargés.

Donc, si vous avez besoin d'accéder à des éléments, vous devez attendre que le DOM soit chargé (cela ne signifie pas que la page entière est chargée, y compris les images et les choses. Il s'agit uniquement de la structure du document, qui est chargée beaucoup plus tôt, donc vous avez généralement gagné ne remarquez pas de retard), en utilisant l' DOMContentLoadedévénement ou des fonctions comme $.readydans jQuery.

Marian
la source
2
L'événement DOMContentLoaded se déclenchera dès que la hiérarchie DOM aura été entièrement construite, l'événement load le fera lorsque toutes les images et sous-cadres auront fini de se charger.
BeauCielBleu