Aller au contenu principal

[Medium] 📄 var, let, const

Vue d'ensemble​

En JavaScript, il existe trois mots-clés pour déclarer des variables : var, let et const. Bien qu'ils servent tous à déclarer des variables, ils diffèrent en termes de portée, d'initialisation, de redéclaration, de réassignation et de moment d'accès.

Principales différences​

Comportementvarletconst
PortéeFonction ou globaleBlocBloc
InitialisationOptionnelleOptionnelleObligatoire
RedéclarationAutoriséeNon autoriséeNon autorisée
RéassignationAutoriséeAutoriséeNon autorisée
Accès avant déclarationRetourne undefinedLance ReferenceErrorLance ReferenceError

Explication détaillée​

Portée​

La portée de var est la fonction ou la portée globale, tandis que let et const ont une portée de bloc (y compris les fonctions, les blocs if-else ou les boucles for).

function scopeExample() {
var varVariable = 'var';
let letVariable = 'let';
const constVariable = 'const';

console.log(varVariable); // 'var'
console.log(letVariable); // 'let'
console.log(constVariable); // 'const'
}

scopeExample();

console.log(varVariable); // ReferenceError: varVariable is not defined
console.log(letVariable); // ReferenceError: letVariable is not defined
console.log(constVariable); // ReferenceError: constVariable is not defined

if (true) {
var varInBlock = 'var in block';
let letInBlock = 'let in block';
const constInBlock = 'const in block';
}

console.log(varInBlock); // 'var in block'
console.log(letInBlock); // ReferenceError: letInBlock is not defined
console.log(constInBlock); // ReferenceError: constInBlock is not defined

Initialisation​

var et let peuvent être déclarés sans initialisation, tandis que const doit être initialisé au moment de la déclaration.

var varVariable; // Valide
let letVariable; // Valide
const constVariable; // SyntaxError: Missing initializer in const declaration

Redéclaration​

Dans la même portée, var permet la redéclaration de la même variable, tandis que let et const ne le permettent pas.

var x = 1;
var x = 2; // Valide, x vaut maintenant 2

let y = 1;
let y = 2; // SyntaxError: Identifier 'y' has already been declared

const z = 1;
const z = 2; // SyntaxError: Identifier 'z' has already been declared

Réassignation​

Les variables déclarées avec var et let peuvent être réassignées, mais celles déclarées avec const ne le peuvent pas.

var x = 1;
x = 2; // Valide

let y = 1;
y = 2; // Valide

const z = 1;
z = 2; // TypeError: Assignment to a constant variable

Remarque : Bien qu'une variable déclarée avec const ne puisse pas être réassignée, si c'est un objet ou un tableau, son contenu peut toujours être modifié.

const obj = { key: 'value' };
obj.key = 'new value'; // Valide
console.log(obj); // { key: 'new value' }

const arr = [1, 2, 3];
arr.push(4); // Valide
console.log(arr); // [1, 2, 3, 4]

Accès avant la déclaration (Temporal Dead Zone)​

Les variables déclarées avec var sont remontées et automatiquement initialisées à undefined. Les variables déclarées avec let et const sont également remontées, mais pas initialisées. Y accéder avant la déclaration lance un ReferenceError.

console.log(x); // undefined
var x = 5;

console.log(y); // ReferenceError: Cannot access 'y' before initialization
let y = 5;

console.log(z); // ReferenceError: Cannot access 'z' before initialization
const z = 5;

Questions d'entretien​

Question : Le piège classique de setTimeout + var​

Déterminez le résultat de sortie du code suivant :

for (var i = 1; i <= 5; i++) {
setTimeout(function () {
console.log(i);
}, 0);
}

Mauvaise réponse (malentendu courant)​

Beaucoup pensent que la sortie est : 1 2 3 4 5

Sortie réelle​

6
6
6
6
6

Pourquoi ?​

Ce problème implique trois concepts fondamentaux :

1. La portée de fonction de var

// var ne crée pas de portée de bloc dans la boucle
for (var i = 1; i <= 5; i++) {
// i est dans la portée externe, toutes les itérations partagent le même i
}
console.log(i); // 6 (valeur de i après la fin de la boucle)

// Dans le cas de var
{
var i;
i = 1;
i = 2;
i = 3;
i = 4; // boucle terminée
}

2. L'exécution asynchrone de setTimeout

// setTimeout est asynchrone, il s'exécute après la fin du code synchrone actuel
for (var i = 1; i <= 5; i++) {
setTimeout(function () {
// Ce code est placé dans la file d'attente du Event Loop
console.log(i);
}, 0);
}
// La boucle s'exécute d'abord complètement (i devient 6), puis les callbacks de setTimeout commencent à s'exécuter

3. Référence du Closure

// Toutes les fonctions callback de setTimeout référencent le même i
// Quand les callbacks s'exécutent, i est déjà devenu 6

Solutions​

Solution 1 : Utiliser let (recommandé) ★

for (let i = 1; i <= 5; i++) {
setTimeout(function () {
console.log(i);
}, 0);
}
// Sortie : 1 2 3 4 5

// Dans le cas de let
{
let i = 1; // i de la première itération
}
{
let i = 2; // i de la deuxième itération
}
{
let i = 3; // i de la troisième itération
}

Principe : let crée une nouvelle portée de bloc à chaque itération, et chaque callback setTimeout capture la valeur de i de l'itération en cours.

// Équivalent à
{
let i = 1;
setTimeout(function () {
console.log(i);
}, 0);
}
{
let i = 2;
setTimeout(function () {
console.log(i);
}, 0);
}
// ... et ainsi de suite

Solution 2 : Utiliser une IIFE (Expression de Fonction Immédiatement Invoquée)

for (var i = 1; i <= 5; i++) {
(function (j) {
setTimeout(function () {
console.log(j);
}, 0);
})(i);
}
// Sortie : 1 2 3 4 5

Principe : L'IIFE crée une nouvelle portée de fonction, et à chaque itération, la valeur actuelle de i est passée en tant que paramètre j, formant un Closure.

Solution 3 : Utiliser le troisième paramètre de setTimeout

for (var i = 1; i <= 5; i++) {
setTimeout(
function (j) {
console.log(j);
},
0,
i
); // Le troisième paramètre est passé à la fonction callback
}
// Sortie : 1 2 3 4 5

Principe : Le troisième paramètre et les suivants de setTimeout sont passés comme arguments à la fonction callback.

Solution 4 : Utiliser bind

for (var i = 1; i <= 5; i++) {
setTimeout(
function (j) {
console.log(j);
}.bind(null, i),
0
);
}
// Sortie : 1 2 3 4 5

Principe : bind crée une nouvelle fonction et lie la valeur actuelle de i en tant que paramètre.

Comparaison des solutions​

SolutionAvantagesInconvénientsRecommandation
letConcis, moderne, compréhensibleES6+5/5 Fortement recommandé
IIFEBonne compatibilitéSyntaxe complexe3/5 À considérer
Paramètre setTimeoutSimple et directPeu connu4/5 Recommandé
bindStyle fonctionnelLisibilité un peu moindre3/5 À considérer

Questions supplémentaires​

Q1 : Que se passe-t-il avec cette modification ?

for (var i = 1; i <= 5; i++) {
setTimeout(function () {
console.log(i);
}, i * 1000);
}

Réponse : 6 est affiché une fois par seconde, au total 5 fois (respectivement à 1, 2, 3, 4 et 5 secondes).

Q2 : Comment afficher séquentiellement 1, 2, 3, 4, 5 chaque seconde ?

for (let i = 1; i <= 5; i++) {
setTimeout(function () {
console.log(i);
}, i * 1000);
}
// Après 1 seconde : 1
// Après 2 secondes : 2
// Après 3 secondes : 3
// Après 4 secondes : 4
// Après 5 secondes : 5

Points clés en entretien​

Cette question évalue :

  1. Portée de var : Portée de fonction vs portée de bloc
  2. Event Loop : Exécution synchrone vs asynchrone
  3. Closure : Comment les fonctions capturent les variables externes
  4. Solutions : Plusieurs approches avec leurs avantages et inconvénients

Lors de la réponse, il est recommandé de :

  • D'abord donner la bonne réponse (6 6 6 6 6)
  • Expliquer la raison (portée de var + setTimeout asynchrone)
  • Fournir des solutions (préférer let et expliquer les autres options)
  • Démontrer sa compréhension des mécanismes internes de JavaScript

Bonnes pratiques​

  1. Privilégier const : Pour les variables qui n'ont pas besoin d'être réassignées, const améliore la lisibilité et la maintenabilité du code.
  2. Ensuite utiliser let : Quand une réassignation est nécessaire, utiliser let.
  3. Éviter var : Étant donné que la portée et le comportement de Hoisting de var peuvent causer des problèmes inattendus, il est recommandé de l'éviter dans le développement JavaScript moderne.
  4. Attention à la compatibilité des navigateurs : Si le support de navigateurs anciens est nécessaire, des outils comme Babel peuvent transpiler let et const en var.

Sujets connexes​