À propos de ce cours
Cette leçon explique quand utiliser const et quand utiliser let, et pourquoi on n'écrit plus var. Vous verrez que const empêche de remplacer une valeur mais pas de modifier le contenu d'un objet ou d'un tableau, et comment Object.freeze() bloque ce contenu au premier niveau. Vous découvrirez les trois niveaux de portée (globale, de fonction et de bloc), le masquage d'une variable, le hoisting et la zone morte temporelle. Enfin, vous apprendrez à éviter les pièges de var : variable qui sort de son bloc, double déclaration, rattachement à window et bug classique des boucles. Des schémas et cinq activités pratiques accompagnent chaque notion.
-
Objectifs
- Déclarer des variables avec
letetconst, et savoir pourquoi on n'utilise plusvar - Comprendre la notion de portée: globale, de fonction et de bloc
- Expliquer la remontée (hoisting) et la zone morte temporelle
- Distinguer réaffecter une variable et modifier le contenu d'un objet déclaré avec
const - Comprendre le masquage de variables et les fermetures (closures)
- Appliquer ces règles dans les classes TypeScript d'Angular
- Déclarer des variables avec
-
Présentation
- Une variable est un nom qui désigne une valeur stockée en mémoire. En JavaScript, on la crée avec un mot-clé de déclaration:
var,letouconst. - Jusqu'en 2015, il n'existait que
var. Ses règles surprenantes étaient la cause de nombreux bugs. La norme ES6 (ES2015) a introduitletetconst, qui sont aujourd'hui la seule façon recommandée de déclarer une variable. - Choisir le bon mot-clé, c'est indiquer clairement deux choses à la personne qui lira le code :
- où la variable existe : sa portée;
- si sa valeur peut changer : la réaffectation.
- Une variable est un nom qui désigne une valeur stockée en mémoire. En JavaScript, on la crée avec un mot-clé de déclaration:

-
Déclarer avec const
constdéclare une constante: une variable qui reçoit une valeur au moment de sa déclaration et qui ne peut jamais être réaffectée.- La valeur est obligatoire à la déclaration.
const tauxTVA = 0.19;
const nomCentre = 'FormaPlus';
const anneeOuverture = 2010;
// tauxTVA = 0.07;
// TypeError: Assignment to constant variable.
// const devise;
// SyntaxError: Missing initializer in const declaration
- Par convention, les valeurs fixées une fois pour toutes dans le programme (taux, limites, messages) s'écrivent parfois en majuscules:
const TVA = 0.19;,const NOMBRE_MAX_STAGIAIRES = 30;.
Déclarer avec let
letdéclare une variable qu'on peut réaffecter. La valeur initiale est facultative : sans valeur, la variable vautundefined.- On l'utilise pour une valeur qui évolue: un compteur, un total, un résultat calculé en plusieurs étapes.
let total = 0;
const notes = [12, 15, 9];
for (const note of notes) {
total = total + note; // réaffectation : let est nécessaire
}
console.log(total); // 36
let mention; // sans valeur : undefined
if (total / notes.length >= 10) {
mention = 'Admis';
} else {
mention = 'Ajourné';
}
console.log(mention); // Admis
- Remarque : dans une boucle
for…of, on peut écrireconst, car une nouvelle variable est créée à chaque tour. Dans une boucleforclassique (for (let i = 0; …; i++)), il fautlet:i++réaffecte la variable.
const ne rend pas un objet immuable
constinterdit de remplacer la valeur de la variable. Mais si cette valeur est un objet ou un tableau, son contenu reste modifiable.- Pensez à une étiquette collée sur une boîte :
constempêche de recoller l'étiquette sur une autre boîte, pas de changer ce qu'il y a dans la boîte.
const stagiaire = { nom: 'Sami', groupe: 'DEV1' };
stagiaire.groupe = 'DEV2'; // autorisé : on modifie une propriété
stagiaire.actif = true; // autorisé : on ajoute une propriété
console.log(stagiaire); // { nom: 'Sami', groupe: 'DEV2', actif: true }
// stagiaire = { nom: 'Karim' }; // interdit : réaffectation
const notes = [12, 15];
notes.push(18); // autorisé
console.log(notes); // [ 12, 15, 18 ]
// notes = []; // interdit
- Pour empêcher aussi la modification du contenu, on utilise
Object.freeze(). Attention : le gel est superficiel, il ne s'applique pas aux objets imbriqués.
'use strict';
const parametres = Object.freeze({ taux: 0.19, devise: 'DT' });
parametres.taux = 0.07;
// TypeError: Cannot assign to read only property 'taux' of object
// (sans 'use strict', la modification est ignorée sans message)
const formateur = Object.freeze({ nom: 'Ali', adresse: { ville: 'Tunis' } });
formateur.adresse.ville = 'Sfax'; // autorisé : adresse n'est pas gelé
console.log(formateur.adresse.ville); // Sfax
-
La portée des variables
- La portée (scope) est la zone du code où une variable existe et peut être utilisée. Il en existe trois niveaux :
- la portée globale: une variable déclarée en dehors de toute fonction et de tout bloc est visible dans tout le fichier (dans tout le module) ;
- la portée de fonction: une variable déclarée dans une fonction n'existe que dans cette fonction ;
- la portée de bloc: une variable
letouconstdéclarée dans un bloc{ }(if,for,while,switch…) n'existe que dans ce bloc.
- Règle de visibilité : un bloc voit les variables des blocs qui l'entourent, mais pas celles des blocs qu'il contient.
- La portée (scope) est la zone du code où une variable existe et peut être utilisée. Il en existe trois niveaux :

const centre = 'FormaPlus'; // portée globale
function afficherGroupe() {
const groupe = 'DEV1'; // portée de la fonction
if (true) {
const salle = 'B12'; // portée du bloc
console.log(centre, groupe, salle); // FormaPlus DEV1 B12
}
console.log(salle); // ReferenceError: salle is not defined
}
afficherGroupe();
console.log(groupe); // ReferenceError: groupe is not defined
- Bonne pratique : déclarer chaque variable dans la portée la plus petite possible, au plus près de son utilisation. Moins une variable est visible, moins elle risque d'être modifiée par erreur.
- Les blocs d'un
switchpartagent la même portée : pour déclarer une variable du même nom dans plusieurscase, on ajoute des accolades.
const role = 'admin';
switch (role) {
case 'admin': {
const message = 'Accès total';
console.log(message);
break;
}
case 'stagiaire': {
const message = 'Accès limité'; // pas de conflit grâce aux accolades
console.log(message);
break;
}
}
-
Le masquage (shadowing)
- Une variable déclarée dans un bloc peut porter le même nom qu'une variable d'une portée extérieure : elle la masque à l'intérieur du bloc, sans la modifier.
const message = 'global';
{
const message = 'bloc'; // nouvelle variable, qui masque la première
console.log(message); // bloc
}
console.log(message); // global : la variable globale n'a pas changé
- C'est autorisé, mais souvent source de confusion. Préférez des noms différents et explicites (
messageGlobal,messageErreur…).
La remontée (hoisting) et la zone morte temporelle
- Avant d'exécuter un bloc, JavaScript parcourt ses déclarations et réserve les variables : c'est la remontée (hoisting). Mais les trois mots-clés ne réagissent pas de la même façon :
- avec
var, la variable existe dès le début et vautundefinedjusqu'à sa déclaration : pas d'erreur, mais une valeur trompeuse ; - avec
letetconst, la variable est réservée mais inaccessible jusqu'à la ligne de sa déclaration. Cette zone s'appelle la zone morte temporelle (Temporal Dead Zone) : toute utilisation provoque uneReferenceError.
- avec

console.log(a); // undefined
var a = 3;
console.log(b); // ReferenceError: Cannot access 'b' before initialization
let b = 3;
- Les déclarations de fonction (
function nom() { }) sont remontées entièrement : on peut les appeler avant leur définition. Ce n'est pas le cas d'une fonction rangée dans une constante :
console.log(direBonjour()); // Bonjour : fonction remontée
function direBonjour() {
return 'Bonjour';
}
saluer(); // ReferenceError: Cannot access 'saluer' before initialization
const saluer = () => 'Salut';
-
Les pièges de var
vara une portée de fonction: il ignore les blocs{ }. Une variable déclarée dans unifou une boucle « fuit » dans toute la fonction.
function testerPortee() {
if (true) {
var v = 1;
let l = 2;
}
console.log(v); // 1 : var a fui hors du bloc
console.log(typeof l); // undefined : let est resté dans le bloc
}
testerPortee();
varautorise la redéclaration d'une même variable, ce qui peut écraser une valeur sans le moindre avertissement.letetconstl'interdisent :
var total = 10;
var total = 20; // aucune erreur : la première valeur est perdue
console.log(total); // 20
let nombre = 1;
// let nombre = 2; // SyntaxError: Identifier 'nombre' has already been declared
- Dans un navigateur, une variable
vardéclarée au niveau global d'un script classique devient une propriété de l'objetwindow. Elle peut alors entrer en conflit avec une autre bibliothèque ou un autre script. Ce n'est pas le cas deletetconst:
<script>
var compteurVar = 1;
let compteurLet = 2;
console.log(window.compteurVar); // 1
console.log(window.compteurLet); // undefined
</script>
- Le piège le plus célèbre concerne les boucles qui créent des fonctions exécutées plus tard :
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log('var', i), 0);
}
for (let j = 0; j < 3; j++) {
setTimeout(() => console.log('let', j), 0);
}
// var 3 var 3 var 3
// let 0 let 1 let 2

-
Les fermetures (closures)
- Une fonction se souvient des variables de la portée où elle a été créée, même après la fin de cette portée. C'est ce qu'on appelle une fermeture (closure).
- Combinée à
let, la fermeture permet de créer une variable privée, accessible uniquement par les fonctions qui l'entourent :
function creerCompteur() {
let valeur = 0; // invisible de l'extérieur
return () => ++valeur; // la fonction renvoyée se souvient de valeur
}
const suivant = creerCompteur();
console.log(suivant(), suivant(), suivant()); // 1 2 3
console.log(typeof valeur); // undefined
- C'est aussi grâce aux fermetures que la boucle avec
letfonctionne : chaque fonction créée dans la boucle se souvient de sa variablej.
let et const dans Angular
- Dans une méthode d'un composant ou d'un service, on déclare les variables locales avec
constetlet, exactement comme en JavaScript. - En revanche, les propriétés d'une classe (les données du composant) se déclarent sans mot-clé. Pour empêcher leur réaffectation, TypeScript fournit le modificateur
readonly, l'équivalent deconstpour une propriété.
import { Component } from '@angular/core';
@Component({
selector: 'app-panier',
templateUrl: './panier.html',
})
export class Panier {
readonly tauxTVA = 0.19; // propriété non réaffectable
articles: number[] = [45, 20]; // propriété modifiable (sans let ni const)
total() {
let somme = 0; // variable locale réaffectée : let
for (const prix of this.articles) {
somme += prix;
}
const ttc = somme * (1 + this.tauxTVA); // variable locale fixe : const
return ttc.toFixed(3);
}
}
- Les erreurs signalées par le compilateur TypeScript si on se trompe :
| Code écrit dans la classe | Message du compilateur |
|---|---|
const tauxTVA = 0.19; (propriété) |
TS1248: A class member cannot have the 'const' keyword. |
let articles: number[] = []; (propriété) |
TS1068: Unexpected token. A constructor, method, accessor, or property was expected. |
this.tauxTVA = 0.07; (propriété readonly) |
TS2540: Cannot assign to 'tauxTVA' because it is a read-only property. |
somme = 10; (variable locale const) |
TS2588: Cannot assign to 'somme' because it is a constant. |
- À retenir pour Angular :
constetletdans les méthodes, rien (oureadonly) devant les propriétés de la classe. Vous retrouvez ainsi dans le code généré par Angular :protected readonly title = signal('mon-projet');.
Activité 1 : prédire les portées
- Dans le dossier
rappel-ecmascriptde la leçon précédente (dépôt GitHub), créer le fichier09-portees.jset y recopier le code suivant. Avant de l'exécuter, compléter chaque commentaire// ?par l'affichage attendu, ou par le nom de l'erreur.
const niveau = 'global';
function tester() {
const niveau = 'fonction';
console.log(niveau); // ?
for (let i = 0; i < 2; i++) {
const niveau = `boucle ${i}`;
console.log(niveau); // ?
}
try {
console.log(i); // ?
} catch (e) {
console.log(e.name);
}
if (niveau === 'fonction') {
var drapeau = true;
}
console.log(drapeau); // ?
}
tester();
console.log(niveau); // ?
try {
console.log(compteur); // ?
} catch (e) {
console.log(e.name);
}
let compteur = 5;
- Exécuter avec
node 09-portees.js, corriger vos prédictions et expliquer chaque erreur de prédiction dans un commentaire.
Activité 2 : corriger un code écrit avec var
- Le programme ci-dessous contient deux bugs liés à
var. Le copier dans10-bugs-var.jset l'exécuter.
var messages = [];
for (var i = 1; i <= 3; i++) {
messages.push(function () {
return 'Module ' + i;
});
}
messages.forEach(function (m) {
console.log(m());
});
function calculerPrime(moyenne) {
if (moyenne >= 16) {
var prime = 200;
}
return prime;
}
console.log(calculerPrime(17), calculerPrime(12));
- Le résultat obtenu est :
Module 4
Module 4
Module 4
200 undefined
- Expliquer en commentaire l'origine de chaque bug.
- Remplacer tous les
varparletouconst, puis exécuter de nouveau : quelle erreur apparaît maintenant danscalculerPrime? Pourquoi est-ce une bonne nouvelle ? - Corriger
calculerPrimepour qu'elle renvoie0quand la moyenne est inférieure à 16. Le résultat attendu est :
Module 1
Module 2
Module 3
200 0
-
Activité 3 : un stock protégé par une fermeture
- Dans
11-stock.js, écrire une fonctioncreerStock(produit, quantiteInitiale = 0)qui :- déclare une variable
quantiteavecletet un tableauhistoriqueavecconst; - renvoie un objet contenant trois fonctions fléchées :
ajouter(n),retirer(n)(qui refuse le retrait si le stock est insuffisant et le note dans l'historique) etetat().
- déclare une variable
- Tester avec le code suivant :
- Dans
const claviers = creerStock('Clavier', 10);
claviers.ajouter(5);
claviers.retirer(12);
claviers.retirer(8);
console.log(claviers.etat());
console.log(claviers.quantite);
const souris = creerStock('Souris');
souris.ajouter(3);
console.log(souris.etat());
// Résultat attendu :
// Clavier : 3 en stock (+5, -12, refus -8)
// undefined
// Souris : 3 en stock (+3)
- Répondre en commentaire : pourquoi
claviers.quantitevaut-ilundefined? Les deux stocks partagent-ils la même variablequantite? Pourquoihistoriquepeut-il être déclaré avecconstalors qu'on y ajoute des éléments ?
Activité 4 : const partout où c'est possible
- Dans
12-refactorisation.js, recopier le code ci-dessous, où toutes les variables sont déclarées aveclet. Remplacer parconstchaque variable qui n'est jamais réaffectée, puis vérifier que le programme fonctionne toujours.
let formations = ['Web', 'Réseaux', 'Design'];
let inscrits = [18, 24, 9];
let capacite = 20;
let complet = 0;
let rapport = '';
for (let i = 0; i < formations.length; i++) {
let nom = formations[i];
let nombre = inscrits[i];
let places = capacite - nombre;
if (places <= 0) {
complet++;
rapport += `${nom} : complet\n`;
} else {
rapport += `${nom} : ${places} places\n`;
}
}
let resume = `${complet} formation(s) complète(s)`;
console.log(rapport + resume);
- Combien de variables restent déclarées avec
let? Lesquelles, et pourquoi ? - Bonus : réécrire la boucle avec
formations.forEach((nom, i) => { … }). Une variableletdisparaît-elle ?
Activité 5 : repérer les erreurs dans une classe TypeScript
- Le composant suivant contient quatre erreurs liées à
let,constetreadonly. Les repérer, puis écrire la version corrigée dans13-composant.tsen indiquant le message du compilateur pour chacune.
export class ListeStagiaires {
const titre = 'Mes stagiaires';
readonly capacite = 25;
let stagiaires: string[] = [];
ajouter(nom: string) {
const nombre = this.stagiaires.length;
if (nombre >= this.capacite) {
this.capacite = this.capacite + 5;
}
this.stagiaires.push(nom);
nombre = nombre + 1;
return nombre;
}
}
- Pour vérifier, compiler le fichier depuis votre dossier avec
npx -p typescript tsc --noEmit 13-composant.ts. Le compilateur signale d'abord l'erreur de syntaxe (TS1068: Unexpected token…, causée parletdevant une propriété) ; une fois celle-ci corrigée, il affiche les trois autres (TS1248,TS2540,TS2588). Après correction complète, aucune erreur ne doit s'afficher. - Deux corrections sont possibles pour
capacite: retirerreadonly, ou ne plus modifier la capacité. Laquelle choisissez-vous, et pourquoi ? - Faire un commit pour chaque activité, puis un push :
git commit -m "let, const et portée : activités 1 à 5".
Erreurs fréquentes
| Message | Cause | Solution |
|---|---|---|
TypeError: Assignment to constant variable. |
Réaffectation d'une variable const |
Utiliser let si la valeur doit vraiment changer |
SyntaxError: Missing initializer in const declaration |
const déclarée sans valeur |
Donner la valeur dès la déclaration, ou utiliser let |
SyntaxError: Identifier 'x' has already been declared |
Deux déclarations du même nom dans la même portée | Renommer, ou supprimer la seconde déclaration (réaffecter sans let) |
ReferenceError: Cannot access 'x' before initialization |
Utilisation dans la zone morte temporelle | Déplacer la déclaration avant la première utilisation |
ReferenceError: x is not defined |
Variable utilisée hors de son bloc, ou faute de frappe | Déclarer la variable dans la portée qui l'utilise |
TS1248: A class member cannot have the 'const' keyword. |
const ou let devant une propriété de classe |
Supprimer le mot-clé, ou utiliser readonly |
-
À retenir
constpar défaut ;letseulement si la variable est réaffectée ; plus jamaisvar.letetconstont une portée de bloc: la variable n'existe qu'entre les accolades où elle est déclarée.- Un bloc voit les variables des blocs qui l'entourent, jamais l'inverse.
- Avant sa ligne de déclaration, une variable
letouconstest dans la zone morte temporelle: son utilisation provoque une erreur. constempêche la réaffectation, pas la modification du contenu d'un objet ou d'un tableau.- Dans une classe Angular :
const/letdans les méthodes, rien oureadonlydevant les propriétés.
Une variable bien déclarée dit au lecteur où elle vit et si elle peut changer : avecconstetlet, le code explique lui-même ses intentions.