Publié le 31 décembre 2013
JavaScript nous permet de modifier presque tous les aspects de la page: contenu, style et réponse aux interactions utilisateur. Toutefois, JavaScript peut également bloquer la création du DOM et retarder l'affichage de la page. Pour obtenir des performances optimales, rendez votre code JavaScript asynchrone et éliminez tout code JavaScript inutile du chemin d'affichage critique.
Résumé
- JavaScript peut interroger et modifier le DOM et le CSSOM.
- L'exécution JavaScript bloque sur le CSSOM.
- JavaScript bloque la construction du DOM, sauf si elle est explicitement déclarée comme asynchrone.
JavaScript est un langage dynamique qui s'exécute dans un navigateur et nous permet de modifier presque tous les aspects du comportement de la page: nous pouvons modifier le contenu en ajoutant et en supprimant des éléments de l'arborescence DOM, nous pouvons modifier les propriétés CSSOM de chaque élément, nous pouvons gérer les entrées utilisateur, et bien plus encore. Pour illustrer ce point, voyons ce qui se passe lorsque l'exemple "Hello World" précédent est modifié pour ajouter un court script intégré:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link href="style.css" rel="stylesheet" />
<title>Critical Path: Script</title>
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
<script>
var span = document.getElementsByTagName('span')[0];
span.textContent = 'interactive'; // change DOM text content
span.style.display = 'inline'; // change CSSOM property
// create a new element, style it, and append it to the DOM
var loadTime = document.createElement('div');
loadTime.textContent = 'You loaded this page on: ' + new Date();
loadTime.style.color = 'blue';
document.body.appendChild(loadTime);
</script>
</body>
</html>
JavaScript nous permet d'accéder au DOM et d'extraire la référence au nœud de span masqué. Le nœud n'est peut-être pas visible dans l'arborescence de rendu, mais il est toujours présent dans le DOM. Ensuite, lorsque nous disposons de la référence, nous pouvons modifier son texte (via .textContent) et même remplacer sa propriété de style d'affichage calculée de "none" à "inline". Notre page affiche désormais Bonjour, élèves interactifs !.
JavaScript nous permet également de créer, de styliser, d'ajouter et de supprimer des éléments dans le DOM. Techniquement, l'intégralité de notre page pourrait n'être qu'un seul grand fichier JavaScript qui crée et stylise les éléments un par un. Bien que cela fonctionne, en pratique, il est beaucoup plus simple d'utiliser HTML et CSS. Dans la deuxième partie de notre fonction JavaScript, nous créons un élément div, définissons son contenu textuel, lui appliquons un style et l'ajoutons au corps.
Nous avons ainsi modifié le contenu et le style CSS d'un nœud DOM existant, et ajouté un nœud entièrement nouveau au document. Notre page ne remportera pas de prix de conception, mais elle illustre la puissance et la flexibilité de JavaScript.
Cependant, bien que JavaScript offre de nombreuses fonctionnalités, il crée de nombreuses limitations supplémentaires sur la façon et le moment où la page est affichée.
Tout d'abord, notez que dans l'exemple précédent, notre script intégré se trouve en bas de la page. Pourquoi ? Vous devriez essayer vous-même, mais si nous déplaçons le script au-dessus de l'élément <span>
, vous remarquerez qu'il échoue et se plaint de ne pas trouver de référence à des éléments <span>
dans le document. Autrement dit, getElementsByTagName('span')
renvoie null
. Cela démontre une propriété importante: notre script est exécuté au point exact où il est inséré dans le document. Lorsque l'analyseur HTML rencontre une balise de script, il suspend le processus de création du DOM et cède le contrôle au moteur JavaScript. Une fois l'exécution du moteur JavaScript terminée, le navigateur reprend là où il s'était arrêté et reprend la création du DOM.
En d'autres termes, notre bloc de script ne peut trouver aucun élément plus loin sur la page, car ils n'ont pas encore été traités. Autrement dit, l'exécution de notre script intégré bloque la création du DOM, ce qui retarde également le rendu initial.
Une autre propriété subtile de l'introduction de scripts sur notre page est qu'ils peuvent lire et modifier non seulement le DOM, mais aussi les propriétés CSSOM. C'est exactement ce que nous faisons dans notre exemple lorsque nous modifions la propriété d'affichage de l'élément span de "none" à "inline". Quelles conséquences cela a-t-il ? Nous avons maintenant une condition de concurrence.
Que se passe-t-il si le navigateur n'a pas terminé de télécharger et de créer le CSSOM lorsque nous voulons exécuter notre script ? La réponse n'est pas très bonne pour les performances: le navigateur retarde l'exécution du script et la création du DOM jusqu'à ce qu'il ait terminé le téléchargement et la création du CSSOM.
En résumé, JavaScript introduit de nombreuses nouvelles dépendances entre le DOM, le CSSOM et l'exécution JavaScript. Cela peut entraîner des retards importants dans le traitement et l'affichage de la page à l'écran:
- L'emplacement du script dans le document est important.
- Lorsque le navigateur rencontre une balise de script, la création du DOM est suspendue jusqu'à la fin de l'exécution du script.
- JavaScript peut interroger et modifier le DOM et le CSSOM.
- L'exécution JavaScript est mise en pause jusqu'à ce que le CSSOM soit prêt.
Dans une large mesure, "optimiser le chemin d'affichage critique" signifie comprendre et optimiser le graphique des dépendances entre HTML, CSS et JavaScript.
Blocage de l'analyseur par rapport au JavaScript asynchrone
Par défaut, l'exécution JavaScript est "bloquante pour l'analyseur": lorsque le navigateur rencontre un script dans le document, il doit suspendre la création du DOM, céder le contrôle à l'environnement d'exécution JavaScript et laisser le script s'exécuter avant de poursuivre la création du DOM. Nous l'avons vu à l'œuvre avec un script intégré dans notre exemple précédent. En fait, les scripts intégrés sont toujours bloquants pour l'analyseur, sauf si vous écrivez du code supplémentaire pour différer leur exécution.
Qu'en est-il des scripts inclus à l'aide d'une balise de script ? Prenons l'exemple précédent et extrayons le code dans un fichier distinct:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link href="style.css" rel="stylesheet" />
<title>Critical Path: Script External</title>
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
<script src="app.js"></script>
</body>
</html>
app.js
var span = document.getElementsByTagName('span')[0];
span.textContent = 'interactive'; // change DOM text content
span.style.display = 'inline'; // change CSSOM property
// create a new element, style it, and append it to the DOM
var loadTime = document.createElement('div');
loadTime.textContent = 'You loaded this page on: ' + new Date();
loadTime.style.color = 'blue';
document.body.appendChild(loadTime);
Que vous utilisiez une balise <script> ou un extrait JavaScript intégré, vous vous attendez à ce que les deux se comportent de la même manière. Dans les deux cas, le navigateur met en pause et exécute le script avant de pouvoir traiter le reste du document. Toutefois, dans le cas d'un fichier JavaScript externe, le navigateur doit s'arrêter pour attendre que le script soit extrait du disque, du cache ou d'un serveur distant, ce qui peut ajouter des dizaines à des milliers de millisecondes de retard au chemin de rendu critique.
Par défaut, tout le code JavaScript est bloqué par l'analyseur. Comme le navigateur ne sait pas ce que le script prévoit de faire sur la page, il part du pire et bloque l'analyseur. Un signal indiquant au navigateur que le script n'a pas besoin d'être exécuté au point exact où il est référencé permet au navigateur de continuer à construire le DOM et de laisser le script s'exécuter lorsqu'il est prêt, par exemple après le récupération du fichier à partir du cache ou d'un serveur distant.
Pour ce faire, l'attribut async
est ajouté à l'élément <script>
:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link href="style.css" rel="stylesheet" />
<title>Critical Path: Script Async</title>
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
<script src="app.js" async></script>
</body>
</html>
L'ajout du mot clé "async" à la balise de script indique au navigateur de ne pas bloquer la création du DOM pendant qu'il attend que le script soit disponible, ce qui peut considérablement améliorer les performances.