Cours JavaScript et TypeScript · Module 2: Fonctions, tableaux et objets
Le hoisting en JavaScript
Dans cette leçon8
Bon retour à Jurassic Park ! Dans la leçon sur la portée des variables, nous avons vérifié où vit une variable, c'est-à-dire quelle est sa portée. Aujourd'hui, nous allons répondre à une question tout aussi importante : à partir de quel moment une variable ou une fonction existe réellement. Le mécanisme qui en décide s'appelle le hoisting, et le connaître te protège de la catégorie de bugs la plus sournoise dans les systèmes du parc - ceux qui ne font rien exploser, mais renvoient en silence une mauvaise valeur. Une variable qui existe mais ne contient rien peut être plus dangereuse qu'une variable qui n'existe pas du tout.
Qu'est-ce que le hoisting ?
Le hoisting (en français "remontée") est le comportement de JavaScript qui consiste à déplacer les déclarations de variables et de fonctions en haut de leur portée - de fonction ou globale - dès la phase de compilation, avant que la première ligne de code ne s'exécute. Le moteur parcourt d'abord tout le code et note les noms qui y apparaissent, et seulement ensuite il commence à l'exécuter. C'est pourquoi nous pouvons faire référence à certaines variables et fonctions avant l'endroit où elles sont écrites dans le fichier.
C'est un peu comme l'inventaire dressé avant que les dinosaures n'arrivent dans leurs enclos : le compilateur JavaScript note d'abord "à quoi s'attendre", et seulement ensuite il exécute les opérations concrètes, ligne par ligne. Seules les déclarations remontent - jamais les valeurs qu'on leur affecte.
Chaque variable suit le même cycle de vie. Le moteur analyse d'abord le code, puis crée le contexte d'exécution et y réserve de la mémoire pour les noms déclarés (c'est précisément le moment du hoisting), et seulement alors il exécute le code : la variable reçoit une valeur et elle est utilisée. Quand plus aucune référence ne mène à la valeur, c'est le ramasse-miettes (garbage collector) qui fait le ménage. Pendant la phase de marquage (mark), il marque tout ce que le programme peut encore atteindre et considère le reste comme bon à supprimer, puis, pendant la phase de nettoyage (sweep), il libère sa mémoire.
Le hoisting des fonctions
Les déclarations de fonction sont remontées en entier : le corps de la fonction voyage avec son nom. Nous pouvons donc appeler une fonction avant qu'elle n'apparaisse dans le fichier. Dans la gestion du parc, c'est très pratique, car cela permet de garder la logique principale en haut et de repousser les détails techniques vers le bas.
1// On peut appeler la fonction avant de l'avoir définie
2checkSecurity("Enclos du T-Rex"); // "Vérification de la sécurité : Enclos du T-Rex"
3
4// La déclaration de fonction est remontée
5function checkSecurity(location) {
6 console.log(`Vérification de la sécurité : ${location}`);
7 return true;
8}Cela fonctionne parce que, pendant la phase de compilation, JavaScript remonte en haut de la portée la déclaration entière - le nom avec le corps. Avant que la première instruction ne s'exécute, checkSecurity est déjà une fonction complète, prête à être appelée, et non un emplacement vide qui attend d'être rempli. C'est le seul cas dans tout le langage où une valeur remonte en haut de la portée en même temps que le nom, car une fonction est elle aussi une valeur. Toutes les autres constructions que nous allons voir dans un instant ne remontent que le nom, et rien d'autre.
Attention aux expressions de fonction !
Seules les déclarations de fonction sont remontées en entier. Les expressions de fonction, c'est-à-dire les fonctions affectées à une variable, suivent les règles du hoisting des variables : la variable elle-même remonte, mais pas la fonction qui lui est affectée.
1// Ceci NE fonctionnera PAS correctement
2// feedDinosaur("Rex"); // TypeError: feedDinosaur is not a function
3
4// Seule la déclaration de la variable est remontée, pas l'affectation de la fonction
5var feedDinosaur = function(dinoName) {
6 console.log(`Nourrissage du dinosaure : ${dinoName}`);
7};
8
9// À partir de cette ligne, on peut appeler la fonction
10feedDinosaur("Rex"); // "Nourrissage du dinosaure : Rex"Lis attentivement le message d'erreur, car c'est un indice précieux. Nous n'obtenons pas un ReferenceError, mais un TypeError: feedDinosaur is not a function - le nom feedDinosaur existe déjà, il contient simplement undefined à ce moment-là, et on ne peut pas appeler undefined comme une fonction. Cette petite différence peut réduire le débogage d'une heure à une minute : un ReferenceError nous oriente vers une faute de frappe ou un import oublié, alors qu'un TypeError nous oriente vers l'ordre des lignes dans le fichier. Une déclaration de fonction est disponible dans toute sa portée, une expression de fonction seulement à partir de la ligne de l'affectation.
Les fonctions fléchées fonctionnent de la même façon
Les fonctions fléchées sont elles aussi des expressions, leur corps n'est donc jamais remonté. Comme on les affecte généralement à const ou à let, l'erreur en cas d'appel trop précoce se révèle encore plus ferme qu'avec var.
1// checkFence(); // ReferenceError: Cannot access checkFence before initialization
2
3const checkFence = () => "Clôture OK";
4
5console.log(checkFence()); // "Clôture OK"La différence est importante : avec var, nous avons obtenu un TypeError, ici un ReferenceError. Les deux messages veulent dire la même chose - "trop tôt" - mais la version avec const arrête le programme exactement à l'endroit de l'erreur, au lieu de laisser passer une valeur undefined qui explosera quelques dizaines de lignes plus loin, dans un morceau de code parfaitement innocent. D'où une règle pratique : avant la ligne de définition, on ne peut utiliser que les fonctions déclarées avec function nom() {}. Tout ce qu'on affecte à une variable - expression de fonction classique ou fonction fléchée - ne fonctionne qu'en dessous.
Le hoisting des variables
Pour les variables, le hoisting se comporte différemment selon le mot-clé (var, let, const). C'est de là que viennent la plupart des surprises, alors décortiquons les deux cas.
Le hoisting des variables déclarées avec var
Les variables déclarées avec var sont remontées et immédiatement initialisées à undefined, donc les lire avant la déclaration est parfaitement légal :
1console.log(dinosaurCount); // undefined (pas une erreur !)
2var dinosaurCount = 15;
3console.log(dinosaurCount); // 15Rien n'a cassé - et c'est justement là que se trouve le problème. Le programme continue avec une valeur que personne n'a prévue, et la valeur undefined du compteur de dinosaures se transforme en NaN dans chaque calcul où elle se retrouve. Le rapport affichera NaN au lieu du nombre d'animaux dans un enclos qui en abrite trois, et personne ne recevra le moindre avertissement. C'est plus facile à comprendre si on imagine qu'avant l'exécution, JavaScript réécrit le code ci-dessus sous cette forme :
1var dinosaurCount; // Hoisting - la déclaration remonte en haut
2console.log(dinosaurCount); // undefined
3dinosaurCount = 15; // L'affectation reste là où nous l'avons écrite
4console.log(dinosaurCount); // 15La déclaration a sauté en haut, l'affectation est restée exactement là où nous l'avons écrite. Toute la règle du hoisting tient en une phrase : la déclaration remonte, la valeur ne bouge pas. L'intervalle entre ces deux points est une fenêtre pendant laquelle la variable est accessible, mais vide. Dans les logs, un capteur du parc qui renvoie undefined ressemble de façon inquiétante à un capteur qui ne renvoie rien du tout - or ce sont deux situations complètement différentes, l'une signifie le calme, l'autre une clôture arrachée.
Le hoisting des variables déclarées avec let et const
Les variables déclarées avec let et const (introduites par ES6) sont elles aussi remontées, mais elles ne reçoivent pas la valeur undefined. À la place, elles se retrouvent dans la temporal dead zone, c'est-à-dire la zone morte temporelle - la période qui va du début de l'exécution du bloc jusqu'à la ligne où la variable est effectivement déclarée. Y accéder pendant ce temps se termine par une erreur.
1// Ceci provoquera une erreur :
2// console.log(securityLevel); // ReferenceError: Cannot access securityLevel before initialization
3let securityLevel = "Élevé";
4console.log(securityLevel); // "Élevé"
5
6// Il en va de même pour const :
7// console.log(parkName); // ReferenceError: Cannot access parkName before initialization
8const parkName = "Jurassic Park";
9console.log(parkName); // "Jurassic Park"Il faut savoir distinguer l'un de l'autre deux messages d'erreur :
- une variable qui n'a jamais été déclarée :
ReferenceError: xyz is not defined - une variable
letouconstdans la zone morte temporelle :ReferenceError: Cannot access 'xyz' before initialization
Cette distinction est un cadeau pour qui débogue. Le premier cas signifie que la variable n'existe nulle part - très probablement une faute de frappe ou un import oublié. Le second dit que la variable existe, mais que nous l'avons lue trop tôt, il suffit donc de déplacer la lecture sous la déclaration. Imagine une fonction de deux cents lignes dans laquelle la valeur est lue en haut et affectée en bas : avec var, nous obtenons un undefined silencieux et une heure de recherche à tâtons, avec let, le programme s'arrête immédiatement et montre du doigt la ligne précise. La zone morte temporelle n'est pas une limitation, c'est un système d'alerte précoce.
Les classes aussi restent dans la zone morte temporelle
Les classes se comportent comme let et const, et non comme les déclarations de fonction. Leur nom est remonté, mais tant que le moteur n'a pas exécuté le corps de la classe, toute tentative de l'utiliser se termine par une erreur.
1// const dino = new Dinosaur("Rex"); // ReferenceError: Cannot access Dinosaur before initialization
2
3class Dinosaur {
4 constructor(name) {
5 this.name = name;
6 }
7}
8
9const dino = new Dinosaur("Rex"); // OK
10console.log(dino.name); // "Rex"La règle "on peut définir les fonctions sous l'endroit où on les utilise" ne s'étend donc pas aux classes - une classe doit exister avant le premier new qui crée son instance. Si, par habitude, tu repousses la définition d'une classe à la fin du fichier, c'est précisément ce message que tu verras dans la console, et le remède est banal : déplace la classe au-dessus du code qui l'instancie. Dans les fichiers du parc, cela donne un ordre simple - d'abord les modèles des animaux et des équipements, ensuite la logique qui les utilise, et jamais l'inverse.
Le hoisting et la portée des variables
Le hoisting est étroitement lié à la portée des variables, qu'on appelle aussi leur scope. Les déclarations ne remontent pas en haut du fichier, mais en haut de leur portée, et la portée qui est "la leur" dépend du mot-clé : var s'en tient à la portée de fonction, let et const - à la portée de bloc. Dans la portée globale, la règle reste la même, sauf que le haut est tout simplement le début du programme.
Exemple pour var (portée de fonction)
var ignore complètement les accolades d'un if ou d'une boucle : la déclaration atterrit en haut de la fonction qui la contient, quel que soit l'endroit où nous l'avons écrite.
1function monitorSystem() {
2 console.log(systemStatus); // undefined (hoisting à l'intérieur de la fonction)
3
4 if (true) {
5 var systemStatus = "Online";
6 }
7
8 console.log(systemStatus); // "Online"
9}
10
11monitorSystem();
12// console.log(systemStatus); // ReferenceError: systemStatus is not defined (hors de la portée de la fonction)Ici, le bloc if n'a pas du tout créé de nouvelle portée pour systemStatus. La déclaration a sauté en haut de monitorSystem, donc on peut lire la variable avant même le bloc (elle est alors vide), et après le bloc, elle contient déjà la valeur affectée. En dehors de la fonction, elle disparaît complètement, car la seule frontière que var respecte, c'est la fonction. Les deux console.log avec le même nom donnent donc deux résultats différents - non pas parce qu'il y a deux variables, mais parce que nous regardons une seule variable, une fois avant l'affectation et une fois après.
Les boucles montrent cette différence le plus clairement
Le meilleur terrain d'essai pour comparer les deux portées, ce sont les boucles, car le compteur y est déclaré et utilisé au même endroit. Vérifie ce qui reste après une boucle dont le compteur est créé avec var, et ce qui reste après une boucle avec let.
1for (var index = 0; index < 3; index++) {
2 // ronde des enclos
3}
4console.log(index); // 3 - var survit à la boucle (portée de fonction)
5
6for (let step = 0; step < 3; step++) {
7 // ronde des enclos
8}
9// console.log(step); // ReferenceError - let reste dans la portée de blocAprès la fin de la première boucle, index vaut toujours 3, car il appartient à la portée de fonction et survit tranquillement à l'accolade fermante. La variable step de la seconde boucle a une portée de bloc et disparaît avec la boucle, donc la lire plus bas se termine par une erreur ReferenceError. Cette différence est à l'origine des fameux soucis de compteur dans les boucles avec délai : avec var, tous les appels voient la même valeur finale, avec let, chaque itération reçoit sa propre copie du compteur et garde la sienne.
Exemple pour let et const (portée de bloc)
Avec let et const, chaque paire d'accolades crée une nouvelle portée, et la zone morte temporelle concerne précisément ce bloc, et non la fonction entière.
1function checkEnclosures() {
2 // console.log(enclosureStatus); // ReferenceError (zone morte temporelle)
3
4 if (true) {
5 let enclosureStatus = "Secure";
6 console.log(enclosureStatus); // "Secure"
7 }
8
9 // console.log(enclosureStatus); // ReferenceError (hors de la portée du bloc)
10}
11
12checkEnclosures();Les deux lignes commentées se termineraient par une erreur, mais pour des raisons complètement différentes : la première parce que la variable se trouve encore dans la zone morte temporelle, la seconde parce que le bloc if s'est terminé et que la variable a cessé d'exister. Les accolades sont ici un véritable mur, et c'est exactement ce que nous voulons - une variable de bloc reste dans son enclos. Dans l'exercice qui t'attend juste après cette leçon, tu t'entraîneras sur la portée des variables dans différents contextes : tu créeras des variables avec différentes portées - globale, de fonction et de bloc - et tu observeras leur comportement.
Exemples pratiques de hoisting à Jurassic Park
Exemple 1 : le système de surveillance des dinosaures
Le hoisting des fonctions permet d'organiser un fichier par ordre d'importance, et non par ordre d'exécution. La procédure principale va en haut, les fonctions utilitaires qu'elle utilise tout en bas - et tout fonctionne toujours.
1// Système de surveillance des dinosaures
2
3// Les fonctions principales sont définies en haut pour plus de clarté
4function initMonitoringSystem() {
5 console.log("Initialisation du système de surveillance des dinosaures...");
6
7 // On peut appeler ces fonctions, même si elles sont définies plus loin
8 const healthStatus = checkDinosaurHealth("Rex");
9 const locationData = trackDinosaur("Blue");
10
11 return {
12 health: healthStatus,
13 location: locationData,
14 systemStatus: "Online"
15 };
16}
17
18// Appel de la fonction principale
19const systemStatus = initMonitoringSystem();
20console.log(systemStatus);
21
22// Fonctions utilitaires définies en bas du fichier
23function checkDinosaurHealth(dinoName) {
24 console.log(`Vérification de l'état de santé du dinosaure : ${dinoName}`);
25 return "En bonne santé";
26}
27
28function trackDinosaur(dinoName) {
29 console.log(`Suivi du dinosaure : ${dinoName}`);
30 return { x: 135, y: 270, area: "Secteur B" };
31}L'appel à initMonitoringSystem tombe sur une ligne qu'aucune des fonctions utilitaires n'a encore "atteinte", et pourtant le code s'exécute sans accroc. C'est du pur hoisting de déclarations de fonction : les deux étaient complètes avant que la première instruction ne démarre. Pendant l'exécution du programme, plus rien ne se déplace par magie, et le gain est purement humain - celui qui ouvre ce fichier lit d'abord ce que fait le système, et seulement ensuite de quelle manière.
Exemple 2 : le piège du hoisting
Le même mécanisme peut se retourner contre nous. Le hoisting devient traître quand une fonction intérieure déclare une variable dont le nom existe déjà dans la portée extérieure. Lis attentivement le code et essaie de prédire le résultat avant de regarder l'explication.
1// Un piège lié au hoisting
2
3// Fonction qui vérifie les dispositifs de sécurité
4function checkSecurity() {
5 var fenceStatus = "Offline"; // Remontée en haut de la fonction
6
7 function enableFences() {
8 // La variable locale est remontée en haut de enableFences,
9 // mais jusqu'à la ligne d'initialisation, elle n'a aucune valeur
10 console.log("Statut actuel de la clôture avant le changement :", fenceStatus); // undefined !
11
12 // La variable locale fenceStatus masque celle de la portée supérieure
13 var fenceStatus = "Online";
14 console.log("Nouveau statut de la clôture :", fenceStatus); // "Online"
15 }
16
17 enableFences();
18 console.log("Statut de la clôture dans la fonction principale :", fenceStatus); // "Offline" (non modifié)
19}
20
21checkSecurity();Un piège subtil se cache ici : le console.log à l'intérieur de enableFences affiche undefined, car la variable locale fenceStatus a été remontée en haut de la fonction intérieure et, dès la première ligne, elle masque celle de la portée extérieure, alors qu'elle ne reçoit sa valeur que plus bas. Quant à la variable extérieure, elle ne change jamais non plus - jusqu'à la fin, elle vaut "Offline". Dans le vrai parc, cela donne un panneau d'opérateur qui annonce des clôtures activées, alors que la fonction principale de sécurité les considère toujours comme désactivées.
Exemple 3 : une meilleure approche avec let et const
Remplacer var par let et const élimine toute cette catégorie de problèmes, car une lecture trop précoce arrête le programme au lieu de produire undefined en silence.
1// Une meilleure approche avec let et const
2
3function parkOperations() {
4 // On utilise const pour les valeurs qui ne doivent pas changer
5 const maxVisitors = 2000;
6
7 // On utilise let pour les variables qui vont changer
8 let currentVisitors = 0;
9
10 function admitVisitors(count) {
11 // Avec let et const, lire une variable avant sa déclaration provoque une erreur,
12 // ce qui permet de repérer le problème bien plus facilement
13
14 // Ceci provoquerait une erreur :
15 // console.log(availableSpace); // ReferenceError: Cannot access before initialization
16
17 // D'abord on déclare, ensuite on utilise
18 const availableSpace = maxVisitors - currentVisitors;
19 if (count <= availableSpace) {
20 currentVisitors += count;
21 console.log(`${count} visiteurs admis. Actuellement dans le parc : ${currentVisitors}`);
22 return true;
23 }
24
25 console.log(`Trop de visiteurs ! Places disponibles : ${availableSpace}`);
26 return false;
27 }
28
29 // On teste notre fonction
30 admitVisitors(500); // 500 visiteurs admis
31 admitVisitors(1000); // 1000 visiteurs admis
32 admitVisitors(700); // Trop de visiteurs ! Places disponibles : 500
33
34 return currentVisitors;
35}
36
37console.log(`Nombre total de visiteurs : ${parkOperations()}`);Voyons ce qui n'a pas changé ici : maxVisitors et currentVisitors sont toujours remontés, car toute déclaration est soumise au hoisting. Ce qui a changé, c'est la réaction à l'erreur - la zone morte temporelle transforme une lecture trop précoce en erreur bruyante au lieu d'un undefined discret. Chaque valeur naît exactement là où elle est nécessaire, et pas un instant plus tôt, si bien que la logique des portiques d'entrée se lit de haut en bas, sans avoir à garder les règles du hoisting en tête. C'est exactement ce qui compte en pratique : non pas apprendre le mécanisme par cœur, mais écrire du code dans lequel ce mécanisme ne surprend jamais.
Conseils pratiques sur le hoisting
Déclare toujours tes variables en haut de leur portée - cela réduit au minimum les problèmes liés au hoisting et rend le code beaucoup plus facile à lire.
Préfère
letetconstàvar- les erreurs de zone morte temporelle sont incomparablement plus faciles à diagnostiquer que de mystérieuses valeursundefinedqui se révèlent plusieurs centaines de lignes plus loin.Déclare tes fonctions avant de les utiliser - le hoisting des fonctions fonctionne, mais l'ordre naturel de lecture reste de toute façon le plus lisible pour la personne qui reviendra sur ce code dans six mois.
Évite les déclarations de fonction à l'intérieur des blocs conditionnels - le comportement peut varier d'un environnement à l'autre, il est donc plus sûr d'utiliser dans ce cas une expression de fonction affectée à une variable.
Souviens-toi que le hoisting concerne les déclarations, et non les initialisations - l'affectation d'une valeur se produit exactement là où nous l'avons écrite, jamais avant.
Un exemple plus grand : le système de gestion des incidents du parc
Ci-dessous, tu trouveras un système plus complet de gestion des incidents de Jurassic Park, qui rassemble en un seul endroit tout ce que contient cette leçon : des déclarations de fonction remontées, une variable var au niveau du module, ainsi que let et const utilisés pour l'état qui change et pour l'état qui ne doit pas changer.
1// Système de gestion des incidents de Jurassic Park
2
3// Utilisation du hoisting - on déclare les variables au début
4var activeIncidents = [];
5let alertLevel = "Normal";
6const MAX_INCIDENTS = 5;
7
8// Fonction principale d'initialisation du système
9function initIncidentSystem() {
10 // Cette variable est remontée uniquement à l'intérieur de cette fonction
11 var systemStatus = "Initialisation";
12
13 // On profite du hoisting des fonctions - on peut les appeler avant leur définition
14 logSystemStatus();
15 resetIncidents();
16
17 systemStatus = "Online";
18 logSystemStatus();
19
20 // Fonction locale - visible uniquement à l'intérieur de initIncidentSystem
21 function logSystemStatus() {
22 console.log(`Statut du système : ${systemStatus}`);
23 }
24
25 return {
26 reportIncident, // Référence à une fonction définie plus bas
27 checkAlertLevel, // Fonction définie plus bas
28 getActiveIncidents, // Fonction définie plus bas
29 resetIncidents // Fonction définie à l'intérieur de initIncidentSystem
30 };
31
32 // Fonction locale que l'on renvoie comme partie de l'API
33 function resetIncidents() {
34 console.log("Réinitialisation de la liste des incidents...");
35 activeIncidents = [];
36 updateAlertLevel();
37 }
38}
39
40// Les déclarations de fonction sont remontées, on peut donc les appeler avant leur définition
41// Fonction pour signaler un nouvel incident
42function reportIncident(location, type, severity) {
43 console.log(`Signalement d'incident : ${type} à l'emplacement ${location} (niveau de menace : ${severity})`);
44
45 const incident = {
46 id: generateIncidentId(),
47 location,
48 type,
49 severity,
50 timestamp: new Date().toISOString(),
51 status: "Actif"
52 };
53
54 activeIncidents.push(incident);
55
56 if (activeIncidents.length > MAX_INCIDENTS) {
57 declareEmergency();
58 } else {
59 updateAlertLevel();
60 }
61
62 return incident;
63}
64
65// Génération d'un ID d'incident unique
66function generateIncidentId() {
67 return `INC-${Date.now()}-${Math.floor(Math.random() * 1000)}`;
68}
69
70// Mise à jour du niveau d'alerte en fonction des incidents actifs
71function updateAlertLevel() {
72 const highSeverityCount = activeIncidents.filter(inc => inc.severity === "Élevé").length;
73
74 if (highSeverityCount >= 3) {
75 alertLevel = "Critique";
76 } else if (highSeverityCount > 0 || activeIncidents.length >= 3) {
77 alertLevel = "Renforcé";
78 } else if (activeIncidents.length > 0) {
79 alertLevel = "Vigilance";
80 } else {
81 alertLevel = "Normal";
82 }
83
84 console.log(`Niveau d'alerte mis à jour : ${alertLevel}`);
85}
86
87// Fonction appelée en cas de trop nombreux incidents
88function declareEmergency() {
89 alertLevel = "Évacuation";
90 console.log("ATTENTION ! Trop d'incidents actifs. Nous déclenchons l'évacuation du parc !");
91 // Code pour lancer les protocoles d'évacuation...
92}
93
94// Fonction pour vérifier le niveau d'alerte actuel
95function checkAlertLevel() {
96 return {
97 level: alertLevel,
98 incidentCount: activeIncidents.length,
99 timestamp: new Date().toISOString()
100 };
101}
102
103// Fonction qui renvoie la liste des incidents actifs
104function getActiveIncidents() {
105 return [...activeIncidents]; // On renvoie une copie pour que personne ne modifie l'original
106}
107
108// Initialisation du système
109const incidentSystem = initIncidentSystem();
110
111// Test du système
112console.log("===== TEST DU SYSTÈME DE GESTION DES INCIDENTS =====");
113
114// Vérification de l'état initial
115console.log("Niveau d'alerte initial :", incidentSystem.checkAlertLevel().level);
116
117// Signalement d'incidents
118incidentSystem.reportIncident("Enclos du T-Rex", "Clôture endommagée", "Élevé");
119incidentSystem.reportIncident("Centre des visiteurs", "Panne de courant", "Moyen");
120
121// Vérification de l'état après les signalements
122console.log("Incidents actifs :", incidentSystem.getActiveIncidents().length);
123console.log("Niveau d'alerte actuel :", incidentSystem.checkAlertLevel().level);
124
125// Ajout d'autres incidents de haute priorité
126incidentSystem.reportIncident("Laboratoire", "Fuite de matériel génétique", "Élevé");
127incidentSystem.reportIncident("Enclos des vélociraptors", "Tentative d'évasion", "Élevé");
128
129// Devrait déclencher l'état d'évacuation
130incidentSystem.reportIncident("Secteur B", "Dinosaure hors de son enclos", "Élevé");
131incidentSystem.reportIncident("Centre des opérations", "Panne des systèmes de sécurité", "Élevé");
132
133// Réinitialisation du système
134incidentSystem.resetIncidents();
135console.log("Après la réinitialisation - niveau d'alerte :", incidentSystem.checkAlertLevel().level);Suis l'ordre des événements dans ce fichier. initIncidentSystem appelle logSystemStatus et resetIncidents avant que l'une ou l'autre n'apparaisse dans le code, et resetIncidents se trouve même après l'instruction return - pourtant tout fonctionne, car les déclarations de fonction sont complètes avant l'exécution de la première ligne du corps. Avec les variables, c'est différent : var systemStatus peut être lue dès le tout début de la fonction, mais elle ne reçoit la valeur "Initialisation" que sur sa propre ligne, et activeIncidents, alertLevel et MAX_INCIDENTS ont été placées volontairement tout en haut du fichier, pour que personne ne les lise trop tôt.
Résumé
Le hoisting est un concept important de JavaScript, qui façonne la manière dont notre code s'exécute :
Les déclarations de fonction sont remontées en entier - on peut les utiliser avant leur déclaration.
Les variables var sont remontées et initialisées à
undefined- on peut y faire référence plus tôt, mais jusqu'à l'affectation, elles ne portent aucune valeur utile.Les variables let et const (et avec elles les classes) sont remontées, mais restent dans la zone morte temporelle - une tentative de lecture avant la déclaration se termine par une erreur.
Les expressions de fonction, y compris les fonctions fléchées, suivent les règles des variables - la variable est remontée, la fonction affectée, non.
Comprendre le hoisting est essentiel pour écrire du code JavaScript prévisible. C'est exactement comme à Jurassic Park - connaître les règles et les protocoles suffit pour que la journée se passe sans surprises ni situations dangereuses.
"Le hoisting en JavaScript ressemble à la mise en route matinale du parc", dit le Dr Rex. "Avant l'arrivée des visiteurs, avant que le code ne démarre, une partie des préparatifs se fait toute seule : les protocoles sont prêts, les procédures en place. Mais les vraies données, il faut encore les recueillir au fil de l'eau, et aller les chercher trop tôt ne te dira absolument rien !"
Dans l'une des prochaines leçons, nous nous attaquerons au mot-clé this - un autre élément fondamental de JavaScript, qui sait aussi semer une belle pagaille.
Code de cette leçon : index.js
1// Hoisting en JavaScript - Parc Jurassique
2console.log("Laboratoire du Hoisting");
3console.log("Comprendre le hoisting en JavaScript\n");
4
5// ===========================================
6// 1. QU'EST-CE QUE LE HOISTING ?
7// ===========================================
8console.log("=== 1. Introduction au hoisting ===");
9
10// JavaScript "remonte" les déclarations en haut de la portée
11// On peut utiliser une fonction avant sa déclaration !
12
13roarDinosaur("T-Rex"); // Fonctionne !
14
15function roarDinosaur(name) {
16 console.log(`${name} rugit: ROAAARRR!`);
17}
18
19roarDinosaur("Velociraptor");
20
21console.log("\nLa fonction est utilisable avant sa déclaration grâce au hoisting !");
22
23// ===========================================
24// 2. HOISTING DES FONCTIONS
25// ===========================================
26console.log("\n=== 2. Hoisting des fonctions ===");
27
28// Les déclarations de fonctions sont remontées
29feedDinosaur("Triceratops"); // Fonctionne
30
31function feedDinosaur(dino) {
32 console.log(`Alimentation: ${dino}`);
33}
34
35// Les expressions de fonction ne sont PAS remontées
36// feedDino2("Stegosaurus"); // ERREUR !
37
38const feedDino2 = function(dino) {
39 console.log(`Alimentation: ${dino}`);
40};
41
42feedDino2("Stegosaurus"); // Maintenant, ça fonctionne
43
44// Les fonctions fléchées ne sont PAS remontées non plus
45// feedDino3("Brachiosaurus"); // ERREUR !
46
47const feedDino3 = (dino) => {
48 console.log(`Alimentation: ${dino}`);
49};
50
51feedDino3("Brachiosaurus"); // Maintenant, ça fonctionne
52
53// ===========================================
54// 3. HOISTING DE VAR
55// ===========================================
56console.log("\n=== 3. Hoisting des variables var ===");
57
58console.log("\nAvant la déclaration:");
59console.log("dinoName:", typeof dinoName); // undefined (pas une erreur !)
60
61var dinoName = "T-Rex";
62
63console.log("Après la déclaration:");
64console.log("dinoName:", dinoName); // T-Rex
65
66// En réalité, JavaScript l'interprète ainsi:
67// var dinoName; // Déclaration remontée en haut
68// console.log(dinoName); // undefined
69// dinoName = "T-Rex"; // Affectation
70
71// ===========================================
72// 4. HOISTING DE LET ET CONST
73// ===========================================
74console.log("\n=== 4. Hoisting let et const ===");
75
76// let et const sont aussi remontés, mais...
77// se trouvent dans la "zone morte temporelle" (TDZ)
78
79console.log("\nTentative d'utilisation avant la déclaration:");
80
81// console.log(dinoSpecies); // ReferenceError: Cannot access before initialization
82// console.log(parkName); // ReferenceError: Cannot access before initialization
83
84let dinoSpecies = "Velociraptor";
85const parkName = "Jurassic Park";
86
87console.log("Après la déclaration:");
88console.log("dinoSpecies:", dinoSpecies);
89console.log("parkName:", parkName);
90
91// ===========================================
92// 5. ZONE MORTE TEMPORELLE (TDZ)
93// ===========================================
94console.log("\n=== 5. Zone morte temporelle (TDZ) ===");
95
96function demonstrateTDZ() {
97 console.log("\nDans la fonction - avant la déclaration:");
98
99 // Cette zone est la TDZ pour 'dinosaur'
100 // console.log(dinosaur); // ERREUR dans la TDZ
101
102 let dinosaur = "Triceratops";
103
104 console.log("Après la déclaration:", dinosaur); // Fonctionne
105}
106
107demonstrateTDZ();
108
109// ===========================================
110// 6. DIFFÉRENCES: var vs let vs const
111// ===========================================
112console.log("\n=== 6. Comparaison: var vs let vs const ===");
113
114function compareHoisting() {
115 console.log("\nTest du hoisting:");
116
117 // var - remonté avec la valeur undefined
118 console.log("var dinosaur1:", typeof dinosaur1); // undefined
119 var dinosaur1 = "T-Rex";
120 console.log("var dinosaur1:", dinosaur1); // T-Rex
121
122 // let - remonté, mais dans la TDZ
123 // console.log("let dinosaur2:", dinosaur2); // ERREUR: TDZ
124 let dinosaur2 = "Velociraptor";
125 console.log("let dinosaur2:", dinosaur2); // Velociraptor
126
127 // const - remonté, mais dans la TDZ
128 // console.log("const dinosaur3:", dinosaur3); // ERREUR: TDZ
129 const dinosaur3 = "Triceratops";
130 console.log("const dinosaur3:", dinosaur3); // Triceratops
131}
132
133compareHoisting();
134
135// ===========================================
136// 7. HOISTING DANS LES BLOCS
137// ===========================================
138console.log("\n=== 7. Hoisting dans les blocs ===");
139
140function blockHoisting() {
141 console.log("\nAvant le bloc:");
142 console.log("globalVar:", typeof globalVar); // undefined
143 // console.log("blockLet:", typeof blockLet); // ERREUR
144
145 var globalVar = "Disponible partout";
146
147 if (true) {
148 console.log("\nDans le bloc:");
149 let blockLet = "Seulement dans le bloc";
150 const blockConst = "Seulement dans le bloc (const)";
151
152 console.log("blockLet:", blockLet);
153 console.log("blockConst:", blockConst);
154 console.log("globalVar:", globalVar);
155 }
156
157 console.log("\nAprès le bloc:");
158 console.log("globalVar:", globalVar); // Fonctionne
159 // console.log("blockLet:", blockLet); // ERREUR
160 // console.log("blockConst:", blockConst); // ERREUR
161}
162
163blockHoisting();
164
165// ===========================================
166// 8. HOISTING DANS LES BOUCLES
167// ===========================================
168console.log("\n=== 8. Hoisting dans les boucles ===");
169
170console.log("\nBoucle avec var:");
171for (var i = 0; i < 3; i++) {
172 console.log(` Itération ${i + 1}`);
173}
174console.log("Après la boucle - i:", i); // Fonctionne (i = 3)
175
176console.log("\nBoucle avec let:");
177for (let j = 0; j < 3; j++) {
178 console.log(` Itération ${j + 1}`);
179}
180// console.log("Après la boucle - j:", j); // ERREUR: j is not defined
181
182// ===========================================
183// 9. PROBLÈMES PRATIQUES AVEC LE HOISTING
184// ===========================================
185console.log("\n=== 9. Pièges courants ===");
186
187// Problème 1: Écrasement de fonction
188function problemExample1() {
189 console.log("\nProblème 1: Écrasement");
190
191 function greet() {
192 console.log(" Premier salut");
193 }
194
195 greet(); // Quelle fonction sera appelée ?
196
197 function greet() {
198 console.log(" Deuxième salut (c'est celui-ci qui sera utilisé !)");
199 }
200
201 // JavaScript "remonte" les deux déclarations,
202 // mais la seconde écrase la première
203}
204
205problemExample1();
206
207// Problème 2: var dans une boucle avec setTimeout
208console.log("\nProblème 2: var dans une boucle avec setTimeout");
209
210console.log("Avec var (toutes afficheront 3):");
211for (var k = 0; k < 3; k++) {
212 setTimeout(function() {
213 console.log(` var k = ${k}`);
214 }, 100);
215}
216
217setTimeout(() => {
218 console.log("\nAvec let (afficheront 0, 1, 2):");
219 for (let m = 0; m < 3; m++) {
220 setTimeout(function() {
221 console.log(` let m = ${m}`);
222 }, 100);
223 }
224}, 200);
225
226// ===========================================
227// 10. BONNES PRATIQUES
228// ===========================================
229console.log("\n=== 10. Bonnes pratiques ===");
230
231// BONNES PRATIQUES:
232
233// 1. Déclare les variables au début de la portée
234function goodPractice1() {
235 const parkName = "Jurassic Park";
236 let dinosaurCount = 0;
237 let totalVisitors = 0;
238
239 dinosaurCount = 15;
240 totalVisitors = 1000;
241
242 console.log(`\n${parkName}: ${dinosaurCount} dinosaures, ${totalVisitors} visiteurs`);
243}
244
245goodPractice1();
246
247// 2. Utilise const par défaut, let si nécessaire
248const SECURITY_LEVEL = "ÉLEVÉ";
249let currentStatus = "OPÉRATIONNEL";
250
251console.log(`Sécurité: ${SECURITY_LEVEL}`);
252console.log(`Statut: ${currentStatus}`);
253
254// 3. Évite var
255// var oldWay = "Ne l'utilise pas !"; // MAUVAIS
256
257// 4. Déclare les fonctions avant de les utiliser (lisibilité)
258function wellOrganized() {
259 // Déclarations au début
260 const MAX_CAPACITY = 50;
261 let currentCapacity = 0;
262
263 // Fonctions auxiliaires
264 function addDinosaur(name) {
265 if (currentCapacity < MAX_CAPACITY) {
266 currentCapacity++;
267 console.log(` Ajout de ${name}. Capacité: ${currentCapacity}/${MAX_CAPACITY}`);
268 } else {
269 console.log(` Pas de place pour ${name}`);
270 }
271 }
272
273 // Logique principale
274 console.log("\nSystème de gestion de la capacité:");
275 addDinosaur("T-Rex");
276 addDinosaur("Velociraptor");
277 addDinosaur("Triceratops");
278}
279
280wellOrganized();
281
282// ===========================================
283// 11. EXEMPLE COMPLET
284// ===========================================
285console.log("\n=== 11. Système de surveillance - exemple complet ===");
286
287function createMonitoringSystem() {
288 // Déclarations au début (bonne pratique)
289 const systemName = "Dino Monitor v1.0";
290 let isActive = false;
291 let alertCount = 0;
292
293 // Fonctions auxiliaires
294 function activate() {
295 isActive = true;
296 console.log(`\n${systemName} activé`);
297 }
298
299 function deactivate() {
300 isActive = false;
301 console.log(`${systemName} désactivé`);
302 }
303
304 function sendAlert(message) {
305 if (isActive) {
306 alertCount++;
307 console.log(`Alerte #${alertCount}: ${message}`);
308 } else {
309 console.log("Système inactif - alerte ignorée");
310 }
311 }
312
313 function getStats() {
314 console.log(`\nStatistiques ${systemName}:`);
315 console.log(` Statut: ${isActive ? "Actif" : "Inactif"}`);
316 console.log(` Alertes envoyées: ${alertCount}`);
317 }
318
319 // Interface principale
320 return {
321 start: activate,
322 stop: deactivate,
323 alert: sendAlert,
324 stats: getStats
325 };
326}
327
328const monitor = createMonitoringSystem();
329
330monitor.alert("Test avant activation"); // Ignorée
331monitor.start();
332monitor.alert("Le T-Rex s'approche de la clôture");
333monitor.alert("Niveau de sécurité critique");
334monitor.stats();
335monitor.stop();
336monitor.alert("Test après désactivation"); // Ignorée
337monitor.stats();
338
339console.log("\nFélicitations ! Tu maîtrises maintenant le hoisting en JavaScript !");Tu as repéré une erreur dans cette leçon ?
Tâches pratiques dans le jeu
- Éditeur de code
Déclare une constante globale parkName = 'Jurassic Park'. Écris une fonction checkEnclosure() qui crée une constante locale enclosureName = 'Zone A' et, dans un bloc if (true), crée avec let une variable alarmLevel = 5 puis affiche parkName, enclosureName et alarmLevel dans la console. La fonction renvoie enclosureName. Appelle checkEnclosure(). En dehors de la fonction, enclosureName et alarmLevel ne doivent pas exister.