Comment fonctionne JavaScript dans le navigateur
Moteur, DOM, boucle d’événements, chargement des scripts : comprenez ce qui se passe quand le navigateur exécute votre JavaScript, pour déboguer plus sereinement.

Moteur, DOM, boucle d’événements, chargement des scripts : comprenez ce qui se passe quand le navigateur exécute votre JavaScript, pour déboguer plus sereinement.

Vous écrivez du JavaScript, vous rechargez la page et quelque chose se produit. Mais que fait réellement le navigateur entre le moment où il reçoit votre fichier et celui où un bouton réagit au clic ? Comprendre ce cycle évite beaucoup de bugs mystérieux : script exécuté trop tôt, interface qui se fige, ordre d’exécution surprenant.
Cet article décrit le modèle d’exécution de JavaScript dans le navigateur, de façon simple et sans jargon inutile.
Le langage JavaScript seul ne sait ni afficher une page, ni écouter un clic, ni appeler un serveur. Dans le navigateur, il s’appuie sur trois ensembles :
fetch, minuteries, stockage local… ;Ces API ne font pas partie du langage lui-même. Par exemple, document et fetch n’existent pas dans le langage standard : c’est le navigateur qui les expose. C’est pourquoi un même code ne se comporte pas toujours de la même façon dans Node.js et dans une page web. La présentation de JavaScript sur MDN apporte le contexte général.
Voici la séquence simplifiée :
JavaScript peut donc intervenir à plusieurs moments, et sa position dans la page change son effet. Pour manipuler concrètement les éléments, voyez DOM JavaScript : manipuler la page correctement.
defer, async ou moduleUn script classique placé dans le head bloque l’analyse du HTML : le navigateur doit le télécharger et l’exécuter avant de continuer. Trois attributs changent ce comportement :
<!-- Bloquant : à éviter dans le head -->
<script src="/js/app.js"></script>
<!-- Téléchargé en parallèle, exécuté après l'analyse du HTML, dans l'ordre -->
<script src="/js/app.js" defer></script>
<!-- Téléchargé en parallèle, exécuté dès qu'il est prêt, sans ordre garanti -->
<script src="/js/analytics.js" async></script>
<!-- Module : différé par défaut -->
<script type="module" src="/js/app.js"></script>
En pratique :
defer convient à la plupart de vos scripts d’application, car l’ordre est respecté et le DOM est disponible ;async convient aux scripts indépendants (mesure d’audience, par exemple) ;type="module" active les modules, détaillés dans Modules JavaScript : import, export et ESM.La page MDN sur l’élément script décrit précisément ces attributs.
JavaScript exécute votre code sur un fil principal unique : une seule instruction à la fois. Le moteur suit les appels de fonctions avec une pile d’appels (call stack) : quand une fonction en appelle une autre, celle-ci est empilée ; quand elle se termine, elle est dépilée.
function a() {
b();
}
function b() {
c();
}
function c() {
console.log('Dans c');
}
a(); // la pile contient successivement a, b, c, puis se vide
Une conséquence directe : si une fonction tourne longtemps (grosse boucle, calcul lourd), rien d’autre ne peut s’exécuter sur ce fil, ni les clics, ni le défilement fluide, ni les animations. C’est la cause classique d’une page qui « fige ».
Un message d’erreur en console affiche d’ailleurs cette pile : lisez-le de haut en bas pour retrouver l’enchaînement d’appels qui a mené au problème.
Alors comment faire pour qu’un fetch, un setTimeout ou un clic ne bloquent pas ce fil unique ? Grâce à la boucle d’événements (event loop).
Le principe, simplifié :
console.log('A');
setTimeout(() => console.log('B'), 0);
Promise.resolve().then(() => console.log('C'));
console.log('D');
// Ordre affiché : A, D, C, B
Pourquoi C avant B ? Les fonctions de rappel des promesses passent par une file de microtâches, traitée avant la prochaine tâche (celle de la minuterie). Vous n’avez pas besoin de maîtriser tous les détails au quotidien, mais ce schéma explique la plupart des ordres d’exécution surprenants. Pour passer à la pratique, lisez Promesses et async/await en JavaScript.
Le navigateur met à jour l’affichage entre les tâches, pas pendant l’exécution d’une fonction synchrone. Si votre code modifie dix fois le DOM dans une même boucle, l’écran ne s’actualise qu’après. Conséquence : une longue tâche empêche aussi l’affichage de se rafraîchir. Découpez le travail, ou déléguez-le (par exemple à un Web Worker pour un calcul lourd).
Une page interactive repose sur des événements : clic, saisie, soumission de formulaire, fin de chargement, redimensionnement.
const bouton = document.querySelector('#envoyer');
bouton.addEventListener('click', () => {
console.log('Clic reçu');
});
Lorsqu’un événement se produit, le navigateur place la fonction de rappel correspondante dans la file ; elle s’exécutera quand le fil principal sera libre. Un écouteur doit donc rester court, sans quoi l’interface devient poussive. Les événements se propagent aussi dans l’arbre du DOM (phase de capture puis de bulle), ce qui permet la délégation : un écouteur sur le parent pour gérer ses enfants.
Pour protéger l’utilisateur, le navigateur impose des limites à votre code :
Côté sécurité, retenez aussi qu’insérer du texte non maîtrisé avec innerHTML expose à des injections de script (XSS) ; privilégiez textContent quand vous n’avez pas besoin de balisage.
Les outils intégrés du navigateur sont votre meilleur allié.
Quelques réflexes efficaces :
console.log ;null. Utilisez defer ou type="module".<script> bloquant dans le head : le rendu est retardé.await, ou ce qui se trouve dans un .then(), est garanti d’arriver après la fin de l’opération.Si votre page est lente ou se comporte mal selon les navigateurs, un audit de site aide à repérer les causes techniques.
Une fois ce modèle en tête, enchaînez avec :
Si vous préférez être accompagné, découvrez Academy Yunt et son blog.
Dans le navigateur, JavaScript s’exécute sur un fil principal, avec une pile d’appels, une boucle d’événements et des API Web fournies par le navigateur. Le chargement des scripts (defer, async, module), la brièveté des tâches et la compréhension de l’ordre d’exécution expliquent la majorité des problèmes rencontrés au début.
Retenez ce réflexe : quand quelque chose ne se passe pas comme prévu, demandez-vous à quel moment votre code s’exécute et ce qui occupe le fil principal à cet instant. Les outils de développement vous donnent la réponse.
Votre code s’exécute sur un fil principal unique dans la page. Le navigateur, lui, effectue d’autres travaux en parallèle (réseau, rendu), et des Web Workers permettent de lancer des calculs sur d’autres fils.
JavaScript est le langage. Node.js est un environnement d’exécution hors navigateur, avec ses propres API (fichiers, réseau), sans DOM.
setTimeout(fn, 0) ne s’exécute-t-il pas immédiatement ?Parce que la fonction passe par la file de tâches : elle attend que le code synchrone en cours soit terminé et que la pile soit vide.
defer ?Pour vos scripts d’application qui touchent au DOM, c’est généralement le bon choix. Pour un script totalement indépendant, async peut convenir.
Ouvrez l’onglet Performance des outils de développement, enregistrez une interaction et repérez les longues tâches sur le fil principal.