Variables JavaScript : let, const et var Expliqués

14 min de lecture JavaScript

Introduction

Tu débutes en JavaScript et tu tombes sur trois mots-clés pour déclarer une variable : var, let et const. Lequel choisir ? Tu écris var nom = "Alice", ça marche. Tu remplaces par let, ça marche aussi. Tu essaies const, ça marche encore. Alors pourquoi trois façons de faire la même chose ?

La réponse courte : ce ne sont pas la même chose. Et confondre les trois est l'une des sources de bugs les plus fréquentes chez les développeurs JavaScript, débutants comme confirmés. Un var mal placé dans une boucle, une const qu'on croit immuable alors qu'elle ne l'est pas, un let utilisé avant sa déclaration qui fait planter ton code sans message clair...

Dans cet article, on va disséquer chaque mot-clé, comprendre comment JavaScript gère les variables en coulisses, et surtout te donner les réflexes pour ne plus jamais hésiter. Code en main, pas de blabla théorique.

C'est quoi une variable en JavaScript ?

Avant de comparer var, let et const, mettons-nous d'accord sur ce qu'est une variable. En JavaScript, une variable est un nom qui pointe vers une valeur stockée en mémoire. C'est une étiquette, un alias. Quand tu écris :

let age = 25;

Tu demandes au moteur JavaScript de réserver un espace en mémoire, d'y stocker la valeur 25, et d'associer le nom age à cet espace. Plus tard, quand tu écriras age dans ton code, JavaScript ira chercher la valeur à cet emplacement.

Une variable a trois caractéristiques fondamentales :

C'est précisément sur ces trois axes que var, let et const diffèrent. Voyons ça en détail.

var : l'ancienne méthode et ses pièges

var est le mot-clé originel de JavaScript, présent depuis la toute première version du langage en 1995. Pendant 20 ans, c'était la seule façon de déclarer une variable. Et pendant 20 ans, il a causé des bugs subtils que même des développeurs expérimentés ne voyaient pas venir.

Portée de fonction, pas de bloc

Le premier piège de var : sa portée est limitée à la fonction dans laquelle il est déclaré, pas au bloc. Qu'est-ce que ça veut dire concrètement ?

function exemple() {  if (true) {    var message = "je suis dans le if";  }  console.log(message); // "je suis dans le if"}exemple();

Tu t'attendais peut-être à une erreur sur le console.log, puisque message est déclaré à l'intérieur du if. Mais non : avec var, la variable existe dans toute la fonction, peu importe le bloc où elle a été déclarée. Le if, le for, le while... aucun de ces blocs ne limite la portée d'un var.

Compare avec une variable déclarée en dehors de toute fonction :

var globale = "accessible partout";function test() {  console.log(globale); // "accessible partout"}test();

Un var déclaré en dehors de toute fonction devient une variable globale, attachée à l'objet window dans le navigateur. C'est rarement ce que tu veux.

Redéclaration silencieuse

Deuxième piège : var te laisse redéclarer la même variable sans broncher.

var compteur = 10;var compteur = 20; // Aucune erreur !console.log(compteur); // 20

Dans un petit fichier, ça ne pose pas de problème. Mais dans un fichier de 500 lignes, tu peux écraser une variable sans t'en rendre compte. C'est exactement le genre de bug qui te fait perdre deux heures à chercher pourquoi ta valeur a changé "toute seule". C'est d'ailleurs l'un des pièges classiques que font les débutants : si tu veux en découvrir d'autres, jette un oeil à les erreurs classiques que font les débutants en JavaScript.

Hoisting de var

Troisième piège : le hoisting. Avec var, la déclaration est "remontée" en haut de la fonction, mais pas l'initialisation.

function demo() {  console.log(x); // undefined (pas d'erreur !)  var x = 5;  console.log(x); // 5}demo();

JavaScript interprète ce code comme si tu avais écrit :

function demo() {  var x; // déclaration remontée, valeur = undefined  console.log(x); // undefined  x = 5; // assignation reste à sa place  console.log(x); // 5}

Le fait d'obtenir undefined au lieu d'une erreur est trompeur. Ton code ne plante pas, mais il ne fait pas ce que tu crois. C'est un bug silencieux, le pire genre de bug.

let : la déclaration moderne

Introduit avec ES6 en 2015, let a été créé pour corriger les problèmes de var. Même syntaxe, comportement beaucoup plus prévisible.

Portée de bloc

let a une portée de bloc (block scope). Un bloc, c'est tout ce qui est entre accolades {} : un if, un for, un while, ou même un simple bloc anonyme.

function exemple() {  if (true) {    let message = "je suis dans le if";    console.log(message); // "je suis dans le if"  }  console.log(message); // ReferenceError: message is not defined}exemple();

Cette fois, le console.log en dehors du if provoque une erreur. C'est le comportement attendu : la variable n'existe que dans le bloc où elle a été déclarée. Pas de surprise, pas de fuite de variable.

Pas de redéclaration

Contrairement à var, let interdit la redéclaration dans le même scope :

let compteur = 10;let compteur = 20; // SyntaxError: Identifier 'compteur' has already been declared

Le moteur JavaScript t'arrête immédiatement. Tu sais tout de suite qu'il y a un conflit de noms. C'est un filet de sécurité précieux.

Par contre, tu peux tout à fait réassigner une variable let :

let score = 0;score = 10; // OK, réassignation autoriséescore = score + 5; // OKconsole.log(score); // 15

Temporal Dead Zone (TDZ)

Avec let, la variable est bien "hoistée" (le moteur sait qu'elle existe), mais elle n'est pas initialisée tant que l'exécution n'a pas atteint sa ligne de déclaration. Toute tentative d'accès avant cette ligne provoque une erreur :

function demo() {  console.log(x); // ReferenceError: Cannot access 'x' before initialization  let x = 5;}demo();

Cette zone entre le début du bloc et la ligne de déclaration s'appelle la Temporal Dead Zone (TDZ). C'est beaucoup plus sûr que le undefined silencieux de var : tu obtiens une erreur explicite qui te dit exactement ce qui ne va pas.

const : la valeur par défaut

const est arrivé en même temps que let avec ES6. Il partage toutes ses caractéristiques (portée de bloc, TDZ, pas de redéclaration) avec une contrainte supplémentaire : tu ne peux pas réassigner la variable.

Immutabilité de la référence

const PI = 3.14159;PI = 3.14; // TypeError: Assignment to constant variable.

Une fois que tu as assigné une valeur à une const, c'est définitif. Tu ne peux pas la pointer vers autre chose. C'est exactement ce que tu veux pour les valeurs qui ne doivent pas changer : une URL d'API, un seuil de configuration, un élément du DOM...

const API_URL = "https://api.exemple.com/v1";const MAX_TENTATIVES = 3;const boutonEnvoyer = document.getElementById("submit");

Le piège : const ne rend pas les objets immuables

C'est l'erreur la plus courante avec const. Beaucoup de développeurs pensent que const signifie "la valeur ne peut pas changer". Ce n'est pas le cas. const signifie "la référence ne peut pas changer". La nuance est cruciale avec les objets et les tableaux.

const utilisateur = { nom: "Alice", age: 25 };// Tu peux modifier les propriétés de l'objet :utilisateur.age = 26; // OKutilisateur.email = "alice@exemple.com"; // OKconsole.log(utilisateur); // { nom: "Alice", age: 26, email: "alice@exemple.com" }// Mais tu ne peux pas réassigner la variable :utilisateur = { nom: "Bob" }; // TypeError: Assignment to constant variable.

Même chose avec les tableaux :

const fruits = ["pomme", "banane"];fruits.push("orange"); // OKfruits[0] = "mangue"; // OKconsole.log(fruits); // ["mangue", "banane", "orange"]fruits = ["kiwi"]; // TypeError: Assignment to constant variable.

La variable fruits pointe toujours vers le même tableau en mémoire. Tu peux modifier le contenu de ce tableau, mais tu ne peux pas faire pointer fruits vers un autre tableau.

Rendre un objet vraiment immuable avec Object.freeze

Si tu veux empêcher toute modification des propriétés d'un objet, utilise Object.freeze() :

const config = Object.freeze({  port: 3000,  host: "localhost",  debug: false});config.port = 8080; // Échoue silencieusement (ou TypeError en mode strict)console.log(config.port); // 3000

Attention : Object.freeze() est superficiel. Il ne gèle que le premier niveau de propriétés. Les objets imbriqués restent modifiables :

const config = Object.freeze({  server: { port: 3000 }});config.server.port = 8080; // Ça passe !console.log(config.server.port); // 8080

Pour un gel profond (deep freeze), il faut écrire une fonction récursive ou utiliser une librairie. Mais dans la majorité des cas, const seul suffit à communiquer ton intention.

Le hoisting démystifié

Le hoisting ("remontée") est un mécanisme fondamental de JavaScript. Quand le moteur analyse ton code avant de l'exécuter, il repère toutes les déclarations de variables et de fonctions, et les "enregistre" en mémoire. Mais la façon dont il les traite dépend du mot-clé utilisé.

var : hoisting avec initialisation à undefined

console.log(a); // undefinedvar a = 42;console.log(a); // 42

Le moteur voit var a, enregistre la variable et l'initialise à undefined. L'assignation = 42 n'a lieu qu'au moment de l'exécution de cette ligne.

let et const : hoisting sans initialisation (TDZ)

console.log(b); // ReferenceError: Cannot access 'b' before initializationlet b = 42;
console.log(c); // ReferenceError: Cannot access 'c' before initializationconst c = 42;

Le moteur voit let b et const c, il sait qu'elles existent, mais il ne les initialise pas. Elles sont dans la Temporal Dead Zone (TDZ) jusqu'à leur ligne de déclaration. Toute tentative d'accès dans cette zone provoque une ReferenceError.

Visualisons la TDZ :

{  // -- Début de la TDZ pour 'nom' --  console.log(nom); // ReferenceError  const autreVariable = "ok"; // Pas de rapport avec 'nom'  // -- Toujours dans la TDZ --  const nom = "Alice"; // Fin de la TDZ, 'nom' est initialisée  console.log(nom); // "Alice"}

La TDZ existe pour une bonne raison : elle t'empêche d'utiliser une variable avant de l'avoir définie. C'est un garde-fou qui rend ton code plus fiable et plus lisible.

Le piège classique : var dans une boucle for

C'est probablement le bug le plus célèbre de JavaScript. Il a été posé dans des milliers d'entretiens techniques, et il illustre parfaitement pourquoi var est problématique.

Le problème

for (var i = 0; i < 3; i++) {  setTimeout(function() {    console.log(i);  }, 100);}// Tu t'attends à : 0, 1, 2// Tu obtiens : 3, 3, 3

Pourquoi ? Parce que var i a une portée de fonction (ou globale ici). Il n'y a qu'une seule variable i, partagée par les trois itérations. Quand les callbacks de setTimeout s'exécutent (après la fin de la boucle), la variable i vaut 3. Les trois callbacks lisent la même variable, et sa valeur finale est 3.

La solution avec let

for (let i = 0; i < 3; i++) {  setTimeout(function() {    console.log(i);  }, 100);}// Résultat : 0, 1, 2

Avec let, chaque itération de la boucle crée un nouveau scope de bloc avec sa propre copie de i. Chaque callback capture sa propre version de la variable. Le problème disparaît naturellement, sans astuce ni workaround.

L'ancien workaround avec une IIFE

Avant ES6, la solution classique était d'utiliser une IIFE (Immediately Invoked Function Expression) pour créer un scope de fonction artificiel :

for (var i = 0; i < 3; i++) {  (function(j) {    setTimeout(function() {      console.log(j);    }, 100);  })(i);}// Résultat : 0, 1, 2

Ça fonctionnait, mais c'était verbeux et obscur. Avec let, une seule lettre remplace tout ce mécanisme. C'est l'un des meilleurs exemples de pourquoi ES6 a changé la façon d'écrire du JavaScript.

Tableau comparatif : var vs let vs const

Voici un récapitulatif visuel pour avoir les différences sous les yeux :

Caractéristiquevarletconst
PortéeFonctionBlocBloc
HoistingOui (initialisé à undefined)Oui (TDZ, non initialisé)Oui (TDZ, non initialisé)
RedéclarationAutoriséeInterditeInterdite
RéassignationAutoriséeAutoriséeInterdite
Ajouté à window (global)OuiNonNon
Disponible depuisES1 (1997)ES6 (2015)ES6 (2015)

En résumé rapide :

Bonnes pratiques modernes

Maintenant que tu comprends les différences, voici les règles que suivent les développeurs JavaScript professionnels en 2026.

Règle 1 : const par défaut

Utilise const pour toutes tes déclarations, sauf si tu sais que la valeur devra changer. C'est contre-intuitif au début, mais la majorité de tes variables n'ont pas besoin d'être réassignées.

// ✅ Bon : const par défautconst utilisateurs = await fetchUtilisateurs();const premiereLettre = nom.charAt(0).toUpperCase();const resultat = nombres.filter(n => n > 10);// ❌ Mauvais : let alors que la valeur ne change jamaislet utilisateurs = await fetchUtilisateurs();let premiereLettre = nom.charAt(0).toUpperCase();

Quand tu lis du code et que tu vois const, tu sais immédiatement : "cette variable ne sera pas réassignée plus bas". C'est un signal fort qui rend le code plus facile à comprendre.

Règle 2 : let uniquement pour les réassignations

Réserve let aux cas où tu as vraiment besoin de réassigner la variable : compteurs de boucle, accumulateurs, variables d'état...

// ✅ Bon : let pour un compteurlet total = 0;for (const item of panier) {  total += item.prix;}// ✅ Bon : let pour une variable conditionnellelet message;if (score > 80) {  message = "Excellent !";} else {  message = "Continue !";}// ✅ Encore mieux : utilise un ternaire avec constconst message = score > 80 ? "Excellent !" : "Continue !";

Note le dernier exemple : souvent, tu peux éviter let en restructurant ton code avec un ternaire, un switch, ou une fonction qui retourne la valeur directement.

Règle 3 : jamais var

Il n'y a aucune raison d'utiliser var en JavaScript moderne. Tous les environnements qui comptent (navigateurs, Node.js, Deno, Bun) supportent let et const depuis des années. Si tu travailles sur un projet legacy qui utilise var, c'est un bon candidat pour un refactoring progressif.

Règle 4 : configure ESLint pour t'aider

N'essaie pas de te rappeler ces règles manuellement. Configure ESLint pour les appliquer automatiquement dans ton éditeur :

// .eslintrc.json (extrait){  "rules": {    "no-var": "error",    "prefer-const": "error"  }}

Avec ces deux règles, ESLint fait le travail de mémorisation à ta place. Tu écris du code, et il te corrige en temps réel.

Règle 5 : déclare tes variables au plus près de leur utilisation

Grâce à la portée de bloc de let et const, tu peux déclarer tes variables exactement là où tu en as besoin :

// ❌ Mauvais : déclarations groupées en haut (style var)function traiterCommande(commande) {  const id = commande.id;  const produits = commande.produits;  const total = commande.total;  const reduction = 0;  // ... 50 lignes plus tard ...  // tu utilises 'reduction' ici}// ✅ Bon : déclarations au plus près de l'utilisationfunction traiterCommande(commande) {  const id = commande.id;  validerCommande(id);  const produits = commande.produits;  verifierStock(produits);  const total = calculerTotal(produits);  const reduction = appliquerPromo(total);  // ...}

Quand tu lis le code, tu n'as pas besoin de remonter de 50 lignes pour trouver où une variable a été déclarée. Tout est local, tout est clair.

Récapitulatif

Voici les points essentiels à retenir :

Pour approfondir ces concepts, consulte la documentation MDN sur let et la documentation MDN sur const, ainsi que le guide complet de javascript.info.

Pour aller plus loin

Comprendre var, let et const, c'est une brique fondamentale. Mais le vrai apprentissage passe par la pratique. Lire un article, c'est bien. Ecrire du code et voir les erreurs en temps réel, c'est mieux.

Si tu veux mettre en pratique ce que tu viens d'apprendre, teste nos ateliers interactifs JavaScript sur GoGoKodo. Tu y trouveras des exercices progressifs où tu manipules des variables, des fonctions, des boucles et bien plus, directement dans ton navigateur, avec un retour instantané sur ton code.

Et une fois que tu seras à l'aise avec les fondamentaux, tu pourras explorer des sujets plus avancés comme async/await et les Promises, qui s'appuient sur tout ce que tu viens d'apprendre sur la portée et les closures. Tu peux aussi débuter avec React pour construire des interfaces modernes, ou consulter notre guide pour se reconvertir dans le développement web si tu envisages d'en faire ton métier.

Le chemin est long, mais chaque concept maîtrisé te rend plus solide. Et celui-ci, tu viens de le cocher.