La difference entre var et let en JavaScript : comprendre le scope et la zone mortelle temporelle

Decouvrez les differences entre var et let en JavaScript : scope, redeclaration et zone mortelle temporelle. Guide pratique pour maitriser ES6.

Mahmoud DEVO
Mahmoud DEVO
December 27, 2025 10 min read
La difference entre var et let en JavaScript : comprendre le scope et la zone mortelle temporelle

La difference entre var et let en JavaScript

==============================================

Introduction : L’evolution des declarations de variables en JavaScript

L’histoire des declarations de variables en JavaScript est fascinante et reflète l’evolution du langage lui-meme. Pendant pres de deux decennies, var etait la seule option disponible pour declarer des variables. Cette simplicite apparente cachait cependant de nombreux pieges qui ont cause d’innombrables bugs dans les applications web.

En 1995, lorsque Brendan Eich a cree JavaScript en seulement 10 jours, les decisions de conception etaient guidees par la rapidite plutot que par la robustesse. Le comportement de var, notamment le hoisting et le function scope, semblait intuitif a l’epoque mais s’est avere problematique a mesure que les applications devenaient plus complexes.

L’arrivee d’ECMAScript 2015 (ES6) a marque un tournant majeur. Cette version a introduit let et const, deux nouvelles facons de declarer des variables qui resolvent les problemes historiques de var. Aujourd’hui, ces declarations modernes sont devenues la norme dans le developpement JavaScript professionnel.

Comprendre les differences entre var, let et const n’est pas qu’une question de syntaxe. C’est une competence fondamentale qui vous permettra d’ecrire du code plus previsible, plus maintenable et moins sujet aux bugs. Que vous travailliez sur une application React, un backend Node.js ou un script vanilla JavaScript, cette connaissance est essentielle.

Dans cet article approfondi, nous allons explorer chaque type de declaration en detail, comprendre leurs mecanismes internes, et decouvrir les meilleures pratiques adoptees par la communaute JavaScript moderne. Vous apprendrez non seulement les differences techniques, mais aussi pourquoi ces differences existent et comment les exploiter efficacement.


var en detail : le heritage de JavaScript

Le hoisting complet avec var

Le hoisting (ou “hissage” en francais) est l’un des comportements les plus deroutants de JavaScript pour les developpeurs venant d’autres langages. Avec var, la declaration de la variable est “hissee” au sommet de son scope, mais pas son initialisation.

console.log(message); // undefined (pas d'erreur !)
var message = "Bonjour";
console.log(message); // "Bonjour"

Ce code est interprete par JavaScript comme :

var message; // Declaration hissee au sommet
console.log(message); // undefined
message = "Bonjour"; // Initialisation reste a sa place
console.log(message); // "Bonjour"

Ce comportement peut mener a des bugs subtils car une variable peut etre utilisee avant d’etre explicitement declaree dans le code. Le moteur JavaScript ne signale aucune erreur, rendant le debogage difficile.

function exemplePiege() {
  console.log(compteur); // undefined, pas d'erreur

  for (var compteur = 0; compteur < 5; compteur++) {
    // ...
  }

  console.log(compteur); // 5 - la variable existe toujours !
}

Le function scope : portee limitee a la fonction

Contrairement a de nombreux langages qui utilisent le block scope, var est confine uniquement au scope de la fonction dans laquelle il est declare. Les blocs comme if, for, while ne creent pas de nouveau scope pour var.

function demonstrationFunctionScope() {
  var x = 1;

  if (true) {
    var x = 2; // Meme variable que celle au-dessus !
    console.log(x); // 2
  }

  console.log(x); // 2 - la valeur a ete modifiee
}

demonstrationFunctionScope();

Cette caracteristique rend le code difficile a raisonner, surtout dans les fonctions longues avec de nombreux blocs conditionnels.

function traitementComplexe(donnees) {
  var resultat = [];

  for (var i = 0; i < donnees.length; i++) {
    if (donnees[i].actif) {
      var temp = donnees[i].valeur * 2;
      resultat.push(temp);
    }
  }

  // Attention : i et temp sont accessibles ici !
  console.log(i);    // donnees.length
  console.log(temp); // derniere valeur calculee ou undefined

  return resultat;
}

La redeclaration permise : source de confusion

Avec var, vous pouvez redeclarer la meme variable plusieurs fois sans erreur. JavaScript ne vous avertira pas que vous ecrasez potentiellement une variable existante.

var utilisateur = "Alice";
console.log(utilisateur); // "Alice"

// Plus tard dans le code...
var utilisateur = "Bob"; // Pas d'erreur !
console.log(utilisateur); // "Bob"

// Encore plus tard...
var utilisateur = 42; // Toujours pas d'erreur !
console.log(utilisateur); // 42

Dans un fichier de plusieurs centaines de lignes, ce comportement peut conduire a des bugs tres difficiles a detecter.

Le probleme classique des closures dans les boucles

C’est peut-etre le bug le plus celebre lie a var. Lorsque vous creez des closures dans une boucle, toutes les closures partagent la meme variable.

// Le probleme classique
for (var i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i);
  }, 1000);
}
// Affiche : 3, 3, 3 (pas 0, 1, 2 comme attendu !)

Pourquoi ce comportement ? Parce que var est function-scoped, il n’existe qu’une seule variable i partagee par toutes les callbacks. Au moment ou les callbacks s’executent (apres 1 seconde), la boucle est terminee et i vaut 3.

// Solution historique avec une IIFE (Immediately Invoked Function Expression)
for (var i = 0; i < 3; i++) {
  (function(index) {
    setTimeout(function() {
      console.log(index);
    }, 1000);
  })(i);
}
// Affiche : 0, 1, 2

Cette solution fonctionnait mais etait verbeuse et difficile a lire. Heureusement, let resout elegamment ce probleme.


let en detail : la declaration moderne

Le block scope : une portee intuitive

let introduit le block scope en JavaScript. Une variable declaree avec let n’existe que dans le bloc ou elle est definie (delimite par des accolades {}).

function demonstrationBlockScope() {
  let x = 1;

  if (true) {
    let x = 2; // Variable differente !
    console.log(x); // 2
  }

  console.log(x); // 1 - la variable originale est preservee
}

Ce comportement correspond a ce que font la plupart des autres langages de programmation comme Java, C++, ou Python.

function traitementAvecLet(donnees) {
  let resultat = [];

  for (let i = 0; i < donnees.length; i++) {
    if (donnees[i].actif) {
      let temp = donnees[i].valeur * 2;
      resultat.push(temp);
    }
    // temp n'est pas accessible ici
  }

  // i n'est pas accessible ici non plus
  // console.log(i); // ReferenceError !

  return resultat;
}

La Temporal Dead Zone (TDZ) : protection contre l’acces premature

La Temporal Dead Zone (Zone Mortelle Temporelle) est une caracteristique de securite de let. Une variable existe dans son scope des le debut du bloc, mais elle ne peut pas etre accedee avant sa declaration.

{
  // Debut de la TDZ pour maVariable
  console.log(maVariable); // ReferenceError: Cannot access 'maVariable' before initialization

  let maVariable = "Hello"; // Fin de la TDZ
  console.log(maVariable); // "Hello"
}

La TDZ aide a detecter les erreurs de programmation. Si vous essayez d’utiliser une variable avant de l’avoir declaree, JavaScript vous le signale immediatement.

function exempleTDZ() {
  // La TDZ commence ici pour 'valeur'

  let autreVariable = valeur; // ReferenceError !
  // meme si 'valeur' sera declaree plus bas

  let valeur = 42;
}

Attention, la TDZ s’applique aussi aux parametres de fonction par defaut :

// Erreur : 'b' est dans la TDZ quand 'a' est evalue
function erreurTDZ(a = b, b = 1) {
  return a + b;
}

// Correct : 'a' est deja initialise quand 'b' est evalue
function correctTDZ(a = 1, b = a) {
  return a + b;
}

Pas de redeclaration : securite renforcee

Contrairement a var, let ne permet pas de redeclarer une variable dans le meme scope. Cette restriction previent les erreurs accidentelles.

let utilisateur = "Alice";

// Plus tard dans le meme scope...
let utilisateur = "Bob"; // SyntaxError: Identifier 'utilisateur' has already been declared

Cependant, vous pouvez toujours reassigner une nouvelle valeur :

let utilisateur = "Alice";
utilisateur = "Bob"; // OK, c'est une reassignation, pas une redeclaration
console.log(utilisateur); // "Bob"

Solution elegante au probleme des closures

Avec let, chaque iteration de boucle cree une nouvelle variable. Les closures capturent donc la bonne valeur.

// Solution avec let
for (let i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i);
  }, 1000);
}
// Affiche : 0, 1, 2 (comme attendu !)

Chaque iteration cree une nouvelle liaison pour i, donc chaque callback capture sa propre copie de la variable.

// Exemple plus concret : creation de gestionnaires d'evenements
const boutons = document.querySelectorAll('.bouton');

for (let index = 0; index < boutons.length; index++) {
  boutons[index].addEventListener('click', function() {
    console.log('Bouton ' + index + ' clique');
  });
}
// Chaque bouton affiche son propre index

const : l’immutabilite en JavaScript

Immutabilite de la reference

const declare une constante dont la reference ne peut pas etre modifiee. Une fois assignee, vous ne pouvez pas reassigner une nouvelle valeur.

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

const NOM_APPLICATION = "MonApp";
NOM_APPLICATION = "AutreApp"; // TypeError

const partage les memes caracteristiques que let pour le scope et la TDZ :

{
  console.log(CONSTANTE); // ReferenceError (TDZ)
  const CONSTANTE = 42;
}
// CONSTANTE n'est pas accessible ici (block scope)

Objets et tableaux avec const

Attention ! const rend la reference immutable, pas la valeur elle-meme. Pour les objets et tableaux, vous pouvez modifier leur contenu.

const utilisateur = {
  nom: "Alice",
  age: 25
};

// Modification du contenu : OK
utilisateur.age = 26;
utilisateur.email = "alice@example.com";
console.log(utilisateur); // { nom: "Alice", age: 26, email: "alice@example.com" }

// Reassignation de la reference : ERREUR
utilisateur = { nom: "Bob" }; // TypeError

Meme principe pour les tableaux :

const nombres = [1, 2, 3];

// Modifications du contenu : OK
nombres.push(4);
nombres[0] = 10;
console.log(nombres); // [10, 2, 3, 4]

// Reassignation : ERREUR
nombres = [5, 6, 7]; // TypeError

Object.freeze() pour l’immutabilite profonde

Si vous avez besoin d’un objet veritablement immutable, utilisez Object.freeze() :

const config = Object.freeze({
  apiUrl: "https://api.example.com",
  timeout: 5000,
  maxRetries: 3
});

config.timeout = 10000; // Silencieusement ignore (ou TypeError en mode strict)
console.log(config.timeout); // 5000

Attention, Object.freeze() n’est pas recursif (shallow freeze). Pour une immutabilite profonde, vous devez geler recursivement :

function deepFreeze(obj) {
  Object.keys(obj).forEach(prop => {
    if (typeof obj[prop] === 'object' && obj[prop] !== null) {
      deepFreeze(obj[prop]);
    }
  });
  return Object.freeze(obj);
}

const configProfonde = deepFreeze({
  api: {
    url: "https://api.example.com",
    headers: {
      'Content-Type': 'application/json'
    }
  }
});

Tableau comparatif : var vs let vs const

Caracteristiquevarletconst
ScopeFunction scopeBlock scopeBlock scope
HoistingOui (undefined)Oui (TDZ)Oui (TDZ)
TDZNonOuiOui
RedeclarationPermiseInterditeInterdite
ReassignationPermisePermiseInterdite
Initialisation obligatoireNonNonOui
DisponibiliteToutes versionsES6+ES6+

Exemples illustrant chaque difference

// === SCOPE ===
function testScope() {
  if (true) {
    var varVariable = "var";
    let letVariable = "let";
    const constVariable = "const";
  }
  console.log(varVariable);   // "var"
  // console.log(letVariable);   // ReferenceError
  // console.log(constVariable); // ReferenceError
}

// === HOISTING ===
function testHoisting() {
  console.log(varVariable);   // undefined
  // console.log(letVariable);   // ReferenceError (TDZ)
  // console.log(constVariable); // ReferenceError (TDZ)

  var varVariable = "var";
  let letVariable = "let";
  const constVariable = "const";
}

// === REDECLARATION ===
var x = 1;
var x = 2; // OK

let y = 1;
// let y = 2; // SyntaxError

const z = 1;
// const z = 2; // SyntaxError

// === REASSIGNATION ===
var a = 1;
a = 2; // OK

let b = 1;
b = 2; // OK

const c = 1;
// c = 2; // TypeError

Dans les boucles : exemples detailles

La boucle for classique

// Avec var : une seule variable partagee
console.log("=== Boucle avec var ===");
for (var i = 0; i < 3; i++) {
  console.log("Iteration:", i);
}
console.log("Apres boucle, i =", i); // 3

// Avec let : variable confinee a la boucle
console.log("=== Boucle avec let ===");
for (let j = 0; j < 3; j++) {
  console.log("Iteration:", j);
}
// console.log("Apres boucle, j =", j); // ReferenceError

Closures dans les boucles : comparaison complete

// Probleme avec var
console.log("=== setTimeout avec var ===");
var fonctionsVar = [];
for (var i = 0; i < 3; i++) {
  fonctionsVar.push(function() {
    return i;
  });
}
console.log(fonctionsVar[0]()); // 3
console.log(fonctionsVar[1]()); // 3
console.log(fonctionsVar[2]()); // 3

// Solution avec let
console.log("=== setTimeout avec let ===");
var fonctionsLet = [];
for (let j = 0; j < 3; j++) {
  fonctionsLet.push(function() {
    return j;
  });
}
console.log(fonctionsLet[0]()); // 0
console.log(fonctionsLet[1]()); // 1
console.log(fonctionsLet[2]()); // 2

Boucle for...of et for...in

const fruits = ['pomme', 'banane', 'orange'];

// for...of avec let (recommande)
for (let fruit of fruits) {
  console.log(fruit);
}

// for...of avec const (encore mieux si pas de reassignation)
for (const fruit of fruits) {
  console.log(fruit);
}

// for...in pour les objets
const personne = { nom: 'Alice', age: 25, ville: 'Paris' };
for (const cle in personne) {
  console.log(`${cle}: ${personne[cle]}`);
}

Boucles imbriquees

// Exemple pratique : matrice
const matrice = [
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9]
];

for (let ligne = 0; ligne < matrice.length; ligne++) {
  for (let colonne = 0; colonne < matrice[ligne].length; colonne++) {
    console.log(`Position [${ligne}][${colonne}] = ${matrice[ligne][colonne]}`);
  }
}
// Les variables ligne et colonne ne fuient pas hors de leurs boucles respectives

Bonnes pratiques : guide du developpeur moderne

Regle d’or : const par defaut

La communaute JavaScript moderne recommande d’utiliser const par defaut, et let uniquement quand une reassignation est necessaire.

// RECOMMANDE : const par defaut
const API_URL = 'https://api.example.com';
const utilisateur = { nom: 'Alice', role: 'admin' };
const processData = (data) => data.map(x => x * 2);

// let seulement si reassignation necessaire
let compteur = 0;
let resultat;

function incrementer() {
  compteur++; // Reassignation necessaire
}

function calculer(valeur) {
  resultat = valeur * 2; // Reassignation necessaire
  return resultat;
}

Pourquoi privilegier const ?

  1. Intention claire : Le lecteur sait immediatement que la variable ne changera pas
  2. Moins de bugs : Impossible de reassigner accidentellement
  3. Optimisation : Les moteurs JavaScript peuvent optimiser les constantes
  4. Refactoring plus sur : Deplacer du code est plus facile
// Code avec const : intention claire
const calculerTotal = (items) => {
  const TVA = 0.20;
  const sousTotal = items.reduce((acc, item) => acc + item.prix, 0);
  const total = sousTotal * (1 + TVA);
  return total;
};

// Le lecteur sait que TVA, sousTotal et total ne changeront pas

Quand utiliser let ?

Utilisez let uniquement dans ces cas :

// 1. Compteurs
let compteur = 0;
while (condition) {
  compteur++;
}

// 2. Accumulateurs dans des boucles
let somme = 0;
for (const nombre of nombres) {
  somme += nombre;
}

// 3. Variables qui changent d'etat
let estConnecte = false;
function connecter() {
  estConnecte = true;
}

// 4. Resultats conditionnels
let message;
if (utilisateur.admin) {
  message = "Bienvenue, administrateur";
} else {
  message = "Bienvenue, utilisateur";
}

Ne jamais utiliser var

Il n’y a pratiquement aucune raison d’utiliser var dans du code moderne. Les seules exceptions sont :

  • Maintenance de code legacy (ancien)
  • Compatibilite avec de tres vieux navigateurs (sans transpilation)
// A EVITER
var ancienCode = "problematique";

// PREFERER
const codeModerne = "propre";
let siNecessaire = "mutation";

Pieges courants et comment les eviter

Piege 1 : Confondre immutabilite de reference et de valeur

// PIEGE : croire que const rend l'objet immutable
const config = { debug: true };
config.debug = false; // Fonctionne !

// SOLUTION : utiliser Object.freeze si necessaire
const configFrozen = Object.freeze({ debug: true });
configFrozen.debug = false; // Ignore silencieusement

Piege 2 : Oublier la TDZ

// PIEGE : utiliser une variable avant sa declaration
function piege() {
  console.log(valeur); // ReferenceError !
  const valeur = 42;
}

// SOLUTION : toujours declarer en haut du scope
function correct() {
  const valeur = 42;
  console.log(valeur);
}

Piege 3 : Declarer dans le mauvais scope

// PIEGE : variable inaccessible apres le bloc
if (condition) {
  const resultat = calculer();
}
console.log(resultat); // ReferenceError !

// SOLUTION : declarer avant si necessaire apres
let resultat;
if (condition) {
  resultat = calculer();
}
console.log(resultat); // OK

Piege 4 : Variables globales accidentelles

// PIEGE : oublier le mot-cle de declaration
function piege() {
  variable = "globale"; // Cree une variable globale !
}

// SOLUTION : toujours utiliser const ou let
function correct() {
  const variable = "locale";
}

// MIEUX : activer le mode strict
"use strict";
function strictMode() {
  variable = "erreur"; // ReferenceError en mode strict
}

Piege 5 : const dans les switch sans blocs

// PIEGE : collision de declarations
switch (valeur) {
  case 1:
    const message = "un"; // Meme scope !
    break;
  case 2:
    const message = "deux"; // SyntaxError: identifier already declared
    break;
}

// SOLUTION : utiliser des blocs explicites
switch (valeur) {
  case 1: {
    const message = "un";
    console.log(message);
    break;
  }
  case 2: {
    const message = "deux"; // OK, scope different
    console.log(message);
    break;
  }
}

Conclusion

Comprendre les differences entre var, let et const est fondamental pour tout developpeur JavaScript moderne. Ces trois mots-cles representent l’evolution du langage vers plus de securite et de previsibilite.

Resume des points cles :

  • var : A eviter. Function-scoped, hoiste, redeclarable. Source de nombreux bugs historiques.
  • let : Pour les variables qui doivent etre reassignees. Block-scoped, protege par la TDZ.
  • const : A privilegier par defaut. Memes caracteristiques que let, mais la reference est immutable.

La regle simple a retenir :

  1. Utilisez const par defaut
  2. Utilisez let uniquement si vous devez reassigner
  3. N’utilisez jamais var dans du code nouveau

En appliquant ces principes, vous ecrirez du code JavaScript plus propre, plus maintenable et moins sujet aux bugs. La transition de var vers const/let est l’une des ameliorations les plus significatives apportees par ES6, et maitriser ces concepts vous placera parmi les developpeurs JavaScript competents.

N’hesitez pas a revisiter cet article lorsque vous avez des doutes sur quelle declaration utiliser. Avec la pratique, le choix entre const et let deviendra instinctif, et vous apprecierez la clarte et la securite qu’ils apportent a votre code.

Advertisement

In-Article Ad

Dev Mode

Share this article

Mahmoud DEVO

Mahmoud DEVO

Senior Full-Stack Developer

I'm a passionate full-stack developer with 10+ years of experience building scalable web applications. I write about Vue.js, Node.js, PostgreSQL, and modern DevOps practices.

Enjoyed this article?

Subscribe to get more tech content delivered to your inbox.

Related Articles