L'instruction let permet de déclarer une variable dont la portée est celle du bloc courant, éventuellement en initialisant sa valeur.

Syntaxe

let var1 [= valeur1] [, var2 [= valeur2]] [, ..., varN [= valeurN]];

Paramètres

var1, var2, …, varN
Le nom de la variable. Cela peut être n'importe quel identifiant valide.
valeur1, valeur2, …, valeurN
La valeur à utiliser pour initialiser la variable. Cela peut être n'importe quelle expression légale.

Description

let permet de déclarer des variables dont la portée est limitée à celle du bloc dans lequel elles sont déclarées. Le mot-clé var, quant à lui, permet de définir une variable globale ou locale à une fonction (sans distinction des blocs utilisés dans la fonction).

L'origine du nom let est décrite dans cette réponse (en anglais).

Les portées de bloc avec let

Le mot-clé let permet de définir des variables au sein d'un bloc et des blocs qu'il contient. var permet quant à lui de définir une variable dont la portée est celle de la fonction englobante.

if (x > y) {
  let gamma = 12.7 + y;
  i = gamma * x;
}

function varTest() {
  var x = 1;
  if (true) {
    var x = 2;  // c'est la même variable !
    console.log(x);  // 2
  }
  console.log(x);  // 2
}

function letTest() {
  let x = 1;
  if (true) {
    let x = 2;  // c'est une variable différente
    console.log(x);  // 2
  }
  console.log(x);  // 1
}

Une meilleure lisibilité pour les fonctions internes

let peut parfois permettre de rendre le code plus lisible lorsqu'on utilise des fonctions internes.

var list = document.getElementById("list");

for (let i = 1; i <= 5; i++) {
  let item = document.createElement("LI");
  item.appendChild(document.createTextNode("Élément " + i));

  item.onclick = function (ev) {
    console.log("Clic sur l'élément " + i + ".");
  };
  list.appendChild(item);
}

// Pour obtenir le même effet avec var
// il aurait fallu créer un contexte différent
// avec une fermeture (closure) pour la valeur

for (var i = 1; i <= 5; i++) {
  var item = document.createElement("li");
  item.appendChild(document.createTextNode("Item " + i));

  (function(i) {
    item.onclick = function(ev) {
      console.log("Item " + i + " a reçu un clic.");
    };
  })(i);
  list.appendChild(item);
}

Dans l'exemple précédent, cela fonctionne comme on l'attend car les cinq instances de la fonction anonyme sont liées à cinq instances différentes de i. Si on remplace let par var, on n'obtiendra pas l'effet escompté car on aura une même variable pour cette portée i=6 (au lieu de 5 différentes).

Règles de portées

Les variables déclarées avec let appartiennent à la portée du bloc dans lequel elles sont définies et indirectement aux portées des blocs de ce bloc. D'une certaine façon let fonctionne comme var, la seule différence dans cette analogie est que let fonctionne avec les portées de bloc et var avec les portées des fonctions :

function varTest() {
  var x = 31;
  if (true) {
    var x = 71;  // c'est la même variable !
    console.log(x);  // 71
  }
  console.log(x);  // 71
}

function letTest() {
  let x = 31;
  if (true) {
    let x = 71;  // c'est une variable différente
    console.log(x);  // 71
  }
  console.log(x);  // 31
}

Au niveau le plus haut (la portée globale), let crée une variable globale alors que var ajoute une propriété à l'objet global :

var x = 'global';
let y = 'global2';
console.log(this.x); // "global"
console.log(this.y); // undefine
console.log(y);      // "global2"

Émuler le fonctionnement des interfaces privées

En utilisant l'instruction let avec des constructeurs, on peut créer des interfaces privées sans avoir à utiliser de fermetures :

var Truc;

{
  let porteePrivee = new WeakMap();
  let compteur = 0;

  Truc = function() {
    this.unePropriete = 'toto';
    
    porteePrivee.set(this, {
      cachee: ++compteur,
    });
  };

  Truc.prototype.montrerPublique = function() {
    return this.unePropriete;
  };

  Truc.prototype.montrerPrivee = function() {
    return porteePrive.get(this).cachee;
  };
}

console.log(typeof porteePrivee);
// "undefined"

var truc = new Truc();

console.log(truc);
// Truc {unePropriete: "toto"}

truc.montrerPublique();
// "toto"

truc.montrerPrivee();
// 1

Cette technique permet d'obtenir un état privé « statique ». Ainsi, dans l'exemple qui précède, toutes les instances de UnConstructeur partageront la même portéePrivée.

Zone morte temporaire (Temporal Dead Zone / TDZ)  et les erreurs liées à let

Lorsqu'on redéclare une même variable au sein d'une même portée de bloc, cela entraîne une exception SyntaxError.

if (x) {
  let toto;
  let toto; // SyntaxError
}

Si on redéclare une variable pour le corps d'une fonction, cela ne pose aucun problème :

function faire_quelque_chose() {
  let toto;
  let toto; // Cela fonctionne.
}

Avec ECMAScript 2015 (ES6), let remontera la déclaration variable au début de la portée (au début du bloc) mais pas l'initialisation. Si on fait référence à une variable dans un bloc avant la déclaration de celle-ci avec let, cela entraînera une exception ReferenceError. En effet, la variable est placée dans une « zone morte temporaire » entre le début du bloc et le moment où la déclaration est traitée. Autrement dit, la déclaration est bien remontée mais la variable ne peut pas être utilisée tant que l'affectation (qui n'est pas remontée) n'a pas été effectuée.

function faire_quelque_chose() {
  console.log(truc); // undefined
  console.log(toto); // ReferenceError
  let toto = 2;
  var truc = 1;
}

Il est possible d'obtenir des erreurs au sein de l'instruction Instructions/switch. En effet, il y a un seul bloc implicite pour cette instruction.

switch (x) {
  case 0:
    let toto;
    break;
    
  case 1:
    let toto; // SyntaxError for redeclaration.
    break;
}

Par contre, si on ajoute une instruction de bloc dans la clause case, cela créera une nouvelle portée et empêchera l'erreur :

let x = 1;

switch(x) {
  case 0: {
    let toto;
    break;
  }  
  case 1: {
    let toto;
    break;
  }
}

Autre exemple lié à la zone morte temporaire et aux portées lexicales

Dans l'exemple qui suit, dans l'expression toto + 55, l'identifiant toto fait référence à la variable du bloc courant et non à celle qui est déclarée au dessus et qui a la valeur 33. Dans l'instruction let toto = (toto + 55); l'instruction est bien remontée mais l'endroit où on utilise toto (dans le fragment (toto + 55)) est toujours dans la zone morte temporaire car toto n'a pas encore été affecté.

function test(){
  var toto = 33;
  if (true) {
    let toto = (toto + 55); // ReferenceError: can't access lexical declaration `toto` before initialization
  }
}
test();

Si on utilise let avec un nom de variable qui est le même que celui de l'argument passé à la fonction, on aura une erreur due à la confusion des portées :

function go(n) {
  for (let n of n.a){ // ReferenceError: can't access lexical declaration `n' before initialization
    console.log(n);
  }
}
go({a:[1, 2, 3]});

Les variables déclarées avec let et les boucles for

Le mot-clé let permet de lier des variables localement dans la portée des boucles for. Contrairement au mot-clé var qui lui rend les variables visibles depuis l'ensemble de la fonction qui contient la boucle.

var i=0;
for ( let i=i ; i < 10 ; i++ ) {
  console.log(i);
}

Règles de portées

for (let expr1; expr2; expr3) instruction

Dans cet exemple, expr2, expr3, et instruction sont contenues dans un bloc implicite qui contient la variable de bloc local déclarée avec let expr1.

Exemples

let / var

Lorsqu'il est utilisé dans un bloc, let permet de limiter la portée de la variable à ce bloc. var quant à lui limite la portée de la variable à la fonction.

var a = 5;
var b = 10;

if (a === 5) {
  let a = 4; // La portée est celle du bloc if
  var b = 1; // La portée est celle interne à la fonction

  console.log(a);  // 4
  console.log(b);  // 1
} 

console.log(a); // 5
console.log(b); // 1

let utilisé dans les boucles

Le mot-clé let permet de lier des variables à la portée de la boucle plutôt qu'à celle de la fonction (avec var) :

for (let i = 0; i<10; i++) {
  console.log(i); // 0, 1, 2, 3, 4 ... 9
}

console.log(i); // i n'est pas défini

Spécifications

Spécification État Commentaires
ECMAScript 2015 (6th Edition, ECMA-262)
La définition de 'Let and Const Declarations' dans cette spécification.
Standard Définition initiale. Cette définition n'inclue pas les expressions et blocs let.
ECMAScript Latest Draft (ECMA-262)
La définition de 'Let and Const Declarations' dans cette spécification.
Standard évolutif  

Compatibilité des navigateurs

FonctionnalitéChromeEdgeFirefoxInternet ExplorerOperaSafari
Support simple4112441 2 3111710
FonctionnalitéAndroid webviewChrome for AndroidEdge mobileFirefox for AndroidIE mobileOpera AndroidiOS Safari
Support simple414112441 2 3 ?1710

1. Prior to Firefox 44, let is only available to code blocks in HTML wrapped in a <script type="application/javascript;version=1.7"> block (or higher version) and has different semantics (e.g. no temporal dead zone).

2. Prior to Firefox 46, a TypeError is thrown on redeclaration instead of a SyntaxError.

3. Firefox 54 adds support of let in workers.

Voir aussi

Étiquettes et contributeurs liés au document

 Contributeurs à cette page : SphinxKnight, kdex, doom-fr, teoli, fscholz
 Dernière mise à jour par : SphinxKnight,