Question 1Que se passe-t-il si tu affiches unit sur la ligne qui suit if (true) { const unit = "yen"; } ?
Portée et hoisting — où et à partir de quand une variable est lisible
La portée détermine où une variable est lisible, et le hoisting enregistre les déclarations avant l'exécution. Masquage, TDZ et pourquoi éviter var.
Si tu utilises hors des accolades une valeur calculée à l'intérieur, l'exécution s'arrête. Elle s'arrête aussi quand une ligne lit un réglage déclaré plus bas. Dans les deux cas, la question est de savoir où un nom est utilisable, et à partir de quand.
Cet article traite de la portée, qui détermine où un nom déclaré est lisible, et du hoisting, qui enregistre les déclarations avant que le code ne s'exécute.
Des variables valables seulement entre accolades — la portée de bloc
Imagine que tu veuilles afficher sur la page du panier combien il manque au client pour avoir la livraison gratuite. Tu peux calculer ce montant dans le if qui vérifie la livraison gratuite, mais si tu essaies d'afficher cette valeur hors des accolades, l'exécution s'arrête sur une ReferenceError.
La portée (la zone du code dans laquelle un nom est lisible) est délimitée par des accolades. La zone couverte par une paire d'accolades s'appelle la portée de bloc, et une variable déclarée avec const ou let n'est lisible que dans le bloc le plus proche qui entoure sa déclaration.
const cartTotal = 4800; // Niveau le plus extérieur, hors de toute accolade
function checkShipping(total) {
const freeLine = 5000; // Seulement dans les accolades de checkShipping
if (total < freeLine) {
const shortage = freeLine - total; // Seulement dans les accolades du if
console.log(`Encore ${shortage} yens`); // Encore 200 yens
}
// Les variables déclarées à l'extérieur sont lisibles depuis l'intérieur
console.log(cartTotal); // 4800
// console.log(shortage); // ReferenceError: shortage is not defined
}
checkShipping(cartTotal);
const cartTotal = 4800— lisible depuis n'importe où dans le fichiercheckShipping(cartTotal)— appelle la fonction depuis le niveau le plus extérieur
const freeLine = 5000— lisible seulement dans ces accolades- Le
cartTotalextérieur reste lisible ici console.log(shortage)— hors du if, shortage est illisible :ReferenceError
const shortage = 200— lisible seulement dans ces accoladesfreeLineetcartTotalsont lisibles tous les deux
if n'est plus lisible une fois ses accolades refermées. La lecture ne marche que dans un sens : de l'intérieur vers l'extérieur.Le niveau le plus extérieur, hors de toute accolade, est la portée globale, et les variables qui s'y trouvent sont lisibles de partout. Le corps d'une fonction est lui aussi une paire d'accolades, donc tu ne peux pas lire freeLine depuis l'extérieur de la fonction. La zone délimitée par les accolades d'une fonction s'appelle la portée de fonction.
La déclaration intérieure l'emporte — le masquage (shadowing)
Quand tu ajoutes une fonction qui calcule un prix avec un taux de TVA réduit, il peut t'arriver d'y déclarer une variable taxRate, du même nom que celle de l'extérieur. Si tu ne sais pas quelle valeur est utilisée, tu auras du mal à comprendre pourquoi un résultat est faux.
Si tu déclares dans une portée intérieure un nom qui existe déjà à l'extérieur, c'est la déclaration intérieure qui est utilisée dans ces accolades. On appelle ça le masquage (shadowing : une déclaration intérieure cache une variable extérieure du même nom), et ça se produit aussi bien avec les accolades d'un if qu'avec celles d'une fonction.
const taxRate = 0.1; // Taux fixé à l'extérieur
function withTax(price) {
return Math.floor(price * (1 + taxRate));
}
function withReducedTax(price) {
const taxRate = 0.08; // Même nom, déclaré dans la fonction
return Math.floor(price * (1 + taxRate));
}
console.log(withTax(1000)); // 1100
console.log(withReducedTax(1000)); // 1080
console.log(taxRate); // 0.1 (celui de l'extérieur n'a pas changé)
taxRate trouve une déclaration différente selon l'endroit où tu le lis. Une déclaration intérieure ne modifie pas la valeur extérieure.Un nom est d'abord recherché dans les accolades les plus proches de la ligne qui le lit, puis de plus en plus loin vers l'extérieur. Dans withReducedTax, la recherche s'arrête dès qu'elle trouve 0.08, donc le 0.1 extérieur n'est jamais lu. Un code où un même nom désigne une valeur différente dans chaque fonction est difficile à suivre : dans ton propre code, donne-leur des noms distincts, comme reducedTaxRate.
Sans const, tu affectes la variable extérieure
Si tu écris taxRate = 0.08; dans une fonction sans const, aucune nouvelle variable n'est créée : c'est une affectation au taxRate extérieur. Si celui-ci est un const, l'exécution s'arrête sur une TypeError, mais si c'est un let, la valeur est écrasée, et même un appel ultérieur à withTax(1000) renvoie 1080.
Illisible avant la ligne de déclaration — le hoisting et la TDZ
Imagine que tu veuilles regrouper des réglages comme un taux de points ou des frais de port sur des lignes placées sous le code qui les utilise. Comme tu l'as vu dans l'article sur les fonctions, une déclaration function peut être appelée même si elle est écrite plus bas, mais lire un réglage let ou const avant sa ligne de déclaration arrête l'exécution sur une ReferenceError.
Avant d'exécuter une portée, JavaScript enregistre les noms des déclarations qu'elle contient. C'est ce qu'on appelle le hoisting (les déclarations sont enregistrées avant l'exécution du code). Une déclaration function enregistre son nom et son corps ensemble, donc tu peux l'appeler depuis des lignes situées au-dessus de la déclaration. let et const n'enregistrent que le nom : la valeur n'est placée qu'au moment où la ligne de déclaration s'exécute.
// Le corps de la fonction lit pointRate au moment de l'appel
function calcPoint(amount) {
return Math.floor(amount * pointRate);
}
// Si tu l'appelles ici, la ligne de déclaration de pointRate ne s'est pas encore exécutée
// console.log(calcPoint(3200));
// → ReferenceError: Cannot access 'pointRate' before initialization
let pointRate = 0.05; // Réglages regroupés plus bas
console.log(calcPoint(3200)); // 160 (lisible, car la ligne de déclaration s'est exécutée)
let ne s'est pas exécutée, la valeur n'existe pas, même quand c'est une fonction écrite plus haut qui la lit. Ce qui compte pour la lecture, c'est l'ordre d'exécution, pas l'endroit où le code est écrit.L'intervalle entre l'entrée dans une portée et l'exécution de la ligne de déclaration let s'appelle la TDZ (Temporal Dead Zone : la période pendant laquelle un nom est illisible, jusqu'à l'exécution de sa ligne de déclaration). Si le message d'erreur dit is not defined, le nom n'existe pas du tout dans la portée. S'il dit before initialization, le nom existe, mais tu l'as lu avant que sa ligne de déclaration ne s'exécute.
Enregistré à l'avance avec undefined — pourquoi ne pas utiliser var
Tu croiseras des variables déclarées avec var dans des articles et des bibliothèques anciens, et si tu recopies du code qui fonctionne, var peut venir avec. Mais var se distingue de const et let sur deux points : la zone où la variable est valable, et ce que tu obtiens en la lisant avant sa déclaration.
Une déclaration var est elle aussi enregistrée à l'avance, mais la variable reçoit undefined au même moment. Du coup, la lire sur une ligne située au-dessus de la déclaration ne provoque pas d'erreur : l'exécution continue simplement avec undefined. Elle n'est pas non plus limitée aux accolades : elle est valable dans toute la fonction qui l'entoure.
function checkStock(orderCount) {
// La lire avant la déclaration n'arrête rien : elle contient undefined
console.log(stock); // undefined
var stock = 12;
console.log(stock); // 12
if (orderCount <= stock) {
var status = "Prêt à expédier"; // Déclaré avec var
let note = "Expédié depuis l'entrepôt A"; // Déclaré avec let
}
// var est valable dans toute la fonction, donc lisible en dehors du if
console.log(status); // Prêt à expédier
// console.log(note); // ReferenceError: note is not defined
}
checkStock(3);
if, une valeur var reste lisible sur les lignes qui suivent la fin des accolades. Avec let, l'exécution s'arrête sur la ligne qui la lit hors de sa zone.Une fonction n'a qu'une seule variable par nom déclaré avec var, donc redéclarer le même nom avec var dans un if ne masque rien : ça réécrit la même variable. Le tableau ci-dessous montre, pour chaque type de déclaration, ce qui est stocké quand le nom est enregistré avant l'exécution, et ce que tu obtiens en le lisant au-dessus de la ligne de déclaration.
| Déclaration | Enregistré avant l'exécution | Lu avant la déclaration |
|---|---|---|
| var stock = 12 | Le nom et undefined | Continue avec undefined |
| let stock = 12 | Le nom seul (pas de valeur) | S'arrête sur ReferenceError |
| const stock = 12 | Le nom seul (pas de valeur) | S'arrête sur ReferenceError |
Passer de var à let fait s'arrêter certaines lignes
Quand tu remplaces var par let dans du vieux code, les lignes qui lisaient la variable avant sa déclaration, ou en dehors du if, s'arrêtent sur une ReferenceError. En réalité, ces lignes lisaient depuis le début undefined ou une valeur venue d'autres accolades. Pour les corriger, déplace la déclaration au-dessus de la ligne qui la lit, dans des accolades qui entourent aussi cette ligne.
Vérification des connaissances
Répondez à chaque question une par une.
Question 2En haut du fichier, tu définis une fonction show qui renvoie rate. Tu l'appelles ensuite sur une ligne placée sous let rate = 0.1;. Que renvoie-t-elle ?
Question 3Si tu écris console.log(count); sur une ligne au-dessus de var count = 3;, qu'est-ce qui s'affiche ?