La portée des variables
Où une variable existe : blocs, fonctions et le piège des globales.
Objectifs
À la fin de cette leçon, vous saurez :
- expliquer ce qu'est la portée (scope) d'une variable ;
- prédire où une variable
let/constest visible, bloc par bloc ; - comprendre la règle d'accès descendant : un bloc interne lit les variables des blocs externes ;
- reconnaître le comportement historique de
var, les risques des globales et le piège du shadowing.
🔗 Pour vous rafraîchir la mémoire : conditions et boucles · découper avec des fonctions
Une variable naît, vit et meurt dans une zone
Créer une variable, c'est demander à l'interpréteur : « réserve ce nom ici ». « Ici » n'est pas une formule : c'est une zone précise du code, appelée sa portée (scope). En dehors de cette zone :
- lire la variable → erreur (
ReferenceError) ; - et personne ne se souvient qu'elle a existé.
{
const prix = 10;
}
console.log(prix);
// ReferenceError: prix is not defined
Les accolades { } délimitent un bloc, et let/const confinent la variable dans son bloc. La portée répond donc à deux questions complémentaires : où cette variable est-elle visible ? et jusqu'à quand ? (réponse : jusqu'à la sortie du bloc — souvenez-vous de la stack frame de la leçon précédente).
La portée de bloc : chaque accolade dessine une frontière
Toute paire d'accolades crée une frontière : corps de if, corps de boucle, corps de fonction, ou simple bloc anonyme.
if (true) {
const message = "visible ici seulement";
console.log(message); // OK : on est DANS le bloc
}
console.log(message);
// ReferenceError : le bloc est refermé, message n'existe plus
for (let i = 0; i < 3; i++) {
// i naît à chaque tour... enfin presque : il existe pour CE tour
console.log(i);
}
console.log(i);
// ReferenceError : i vivait dans la boucle
Lisez cela comme une protection, pas une contrainte : vos variables internes ne fuient pas, ne polluent pas l'extérieur, et ne peuvent pas être écrasées par accident depuis ailleurs. Chaque tour de boucle possédant « son » i évite d'ailleurs des bugs célèbres du code ancien.
La règle d'accès descendant : on lit vers l'extérieur, jamais vers l'intérieur
Voici la moitié manquante du modèle, celle qui explique la plupart des exercices. Quand vous utilisez un nom, JavaScript le cherche :
- d'abord dans le bloc courant ;
- puis dans le bloc englobant (la fonction qui contient ce bloc) ;
- puis dans la fonction au-dessus, etc., jusqu'au niveau global ;
- introuvable partout →
ReferenceError.
La lecture descend les étages vers l'extérieur — jamais l'inverse :
const taux = 1.2; // niveau fichier (global)
function prixTTC(ht) { // nouvelle portée (fonction)
const majoration = 5; // locale à la fonction
if (ht > 100) { // nouveau bloc interne
return ht * taux + majoration; // lit taux ✓ (2 niveaux plus haut)
// lit majoration ✓ (1 niveau plus haut)
}
return ht;
}
console.log(prixTTC(200)); // 245 : tout s'est résolu en remontant
// console.log(majoration); // ReferenceError : dehors, on ne voit PAS l'intérieur
Formule à retenir : « l'intérieur voit l'extérieur ; l'extérieur ignore l'intérieur. » C'est exactement pourquoi une fonction peut utiliser une constante déclarée avant elle, sans la recevoir en paramètre — pratique tolérée pour les vraies constantes, dangereuse pour le reste (voir « globales » ci-dessous).
Ce mécanisme porte un nom savant — la chaîne de portée — mais retenez surtout le mouvement : une recherche en cascade vers l'extérieur, arrêtée au premier nom trouvé.
La portée de fonction : un cas particulier utile
Une variable déclarée directement dans une fonction est visible dans toute la fonction, y compris ses blocs internes :
function total(quantite) {
const tarif = 5;
if (quantite > 0) {
return quantite * tarif; // tarif visible : même fonction
}
return 0;
}
Historique à connaître pour lire le code ancien : var ignorait les blocs et ne connaissait que la portée de fonction — une variable var déclarée dans un if existait aussi hors du if. Source inépuisable de bugs, raison de l'invention de let/const. Ce cursus n'utilise jamais var ; si vous en croisez, méfiance et migration.
Les globales : pratiques, puis dangereuses
Une variable déclarée au sommet du fichier est visible partout : c'est une globale. Deux visages :
const TAUX_TVA = 0.2; // globale assumée : constante de référence
function prixTTC(ht) {
return ht * (1 + TAUX_TVA); // lecture légitime
}
Pour une constante partagée (configuration, limites), c'est parfait. Mais dès qu'une globale est modifiable, trois dangers apparaissent :
- n'importe quelle ligne du programme peut la changer, y compris par accident ;
- deux morceaux de code peuvent se marcher dessus sans le savoir ;
- déboguer devient un jeu de piste : qui a modifié cette valeur ?
let solde = 100; // globale MODIFIABLE : bombe à retardement
function depenser(montant) {
solde -= montant; // chacun écrit où il veut...
}
function crediter(montant) {
solde += montant; // ...et personne ne maîtrise l'état global
}
À petite échelle ça passe ; sur un programme réel, ce style produit des bugs impossibles à reproduire. La règle d'or qui découle de toute la leçon :
Déclarez chaque variable à la portée la plus petite possible.
Locale à un bloc ? Parfait. Locale à une fonction ? Très bien. Globale ? Seulement si elle est vraiment partagée — idéalement en const.
Le shadowing : quand deux vies se superposent
Un bloc interne peut redéclarer un nom déjà utilisé dehors : localement, il le masque :
const nom = "extérieur";
function saluer() {
const nom = "intérieur"; // AUTRE variable, masquée localement
console.log(nom); // lit d'abord le bloc courant → "intérieur"
}
saluer(); // intérieur
console.log(nom); // extérieur : intacte
Reliez cela à la règle de recherche : le nom est trouvé au premier étage où il existe — ici dès le bloc courant, donc jamais remonté. Aucune erreur n'est signalée : le code est légal, mais le même mot désigne deux choses selon l'endroit. Relire un tel code exige de suivre les étages mentalement — fatigue et erreurs garanties. Recommandation : interdisez-vous le shadowing volontairement ; certains outils de vérification le signalent d'ailleurs automatiquement.
Attention à la variante piégeuse : oublier la déclaration. Sans const ni let dans la fonction, il n'y a pas de shadowing — la fonction modifie la globale !
let compteur = 0;
function incrementer() {
compteur = compteur + 1; // PAS de déclaration ici : on touche la globale !
}
incrementer();
console.log(compteur); // 1 : la globale a bien été modifiée
Comparez avec la version shadowing de l'exercice 2 ci-dessous : une seule ligne change (const), et le comportement bascule entièrement. C'est toute la différence entre « créer ma propre variable » et « toucher celle de tous ».
Exercice
Prédisez chaque résultat sans exécuter, notez, puis vérifiez.
function test() {
if (true) {
const x = 1;
}
return x;
}
console.log(test());
let compteur = 0;
function incrementer() {
const compteur = 100;
compteur = compteur + 1;
}
incrementer();
console.log(compteur);
-
Modifiez l'exercice 2 (une seule ligne !) pour que l'affichage soit
1. Expliquez précisément pourquoi votre modification fonctionne.
const tarif = 10;
function calcul(qte) {
if (qte > 3) {
const remise = 0.9;
return qte * tarif * remise;
}
return qte * tarif;
}
console.log(calcul(5));
console.log(calcul(2));
Pour chaque accès à une variable, indiquez dans quelle portée elle a été trouvée.
- Expliquez pourquoi
for (let i = 0; ...)permet d'avoir trois boucles imbriquées utilisant toutes une variablei, alors qu'une seule globaleiprovoquerait un chaos immédiat.
Résumé
- Portée = zone où un nom existe ;
let/constont une portée de bloc,var(ancien) de fonction. - Recherche d'un nom : en cascade vers l'extérieur, premier trouvé gagne — l'intérieur lit l'extérieur, jamais l'inverse.
- Globales : réservées aux constantes réellement partagées ; les globales mutables sont un poison.
- Shadowing : masquage local légal mais illisible ; absence de déclaration = modification de la globale.
- Règle d'or : la plus petite portée possible, toujours.
Correction disponibleCherchez d’abord par vous-même.Voir la correction
Correction
Réponses détaillées
Question 1. ReferenceError: x is not defined. x naît dans le bloc if et meurt à son accolade fermante ; le return x se trouve en dehors, à un étage où x n'a jamais existé. Notez que la recherche en cascade ne sauve pas : elle remonte, mais x n'existe à aucun étage extérieur.
Question 2. Affiche 0. Dans la fonction, const compteur = 100 crée une nouvelle variable qui masque la globale (shadowing). L'incrément touche cette locale (101), qui disparaît à la fin de la fonction. La globale n'a jamais été atteinte : la recherche du nom s'était arrêtée au premier étage.
Question 3. Supprimez la ligne const compteur = 100;. Alors la recherche du nom dans la fonction ne trouve rien au niveau local et remonte trouver la globale : compteur = compteur + 1 la lit (0) puis la modifie (1). Une seule ligne sépare « créer mon monde parallèle » de « modifier le monde commun » — voilà pourquoi la présence ou l'absence de déclaration doit être un choix conscient, jamais un hasard.
Question 4. Affiche 45 puis 20.
calcul(5) :
qte → paramètre de calcul (portée fonction)
tarif → globale (recherche : bloc if ✗ → fonction ✗ → global ✓)
remise → bloc if (portée bloc)
calcul(2) :
qte, tarif → comme ci-dessus ; remise jamais utilisée
Le programme entier tient grâce à la règle d'accès descendant : la fonction lit librement ce qui est au-dessus d'elle, ses blocs lisent ce qui est autour, et rien ne fuit vers l'extérieur.
Question 5. Chaque boucle possède son propre bloc : chaque let i crée une variable distincte, vivante uniquement dans sa boucle (et pour les boucles imbriquées, chacune masque naturellement la précédente dans son propre étage). Trois i cohabitent sans collision car ils n'existent pas au même endroit. Avec une globale unique, les trois boucles écriraient et liraient la même case : compteurs qui se marchent dessus, conditions faussées, sortie prématurée. La petite portée n'est pas une contrainte administrative : c'est ce qui rend la concurrence de noms inoffensive.