let

let 文はブロックスコープのローカル変数を宣言します。任意で値を代入して初期化できます。

構文

let var1 [= value1] [, var2 [= value2]] [, ..., varN [= valueN];

引数

var1, var2, …, varN
宣言する変数または複数の変数の名前です。それぞれは JavaScript の正式な識別子である必要があります。
value1, value2, …, valueN 省略可
宣言される変数ごとに、任意で初期値を JavaScript の正式な式で指定することができます。

分割代入構文は、変数の宣言にも使用できます。

let { bar } = foo; // where foo = { bar:10, baz:12 };
/* これは、値が 10 の 'bar' という名前の変数を作成します。*/

解説

let を使用することで、それが使用されたブロック、文または式にスコープを限定した変数を宣言することができます。これは var キーワードのように、変数をブロックスコープに関係なく、グローバルや関数全体のローカルに定義するようなことはありません。他にも、varlet は、後者はパーサーが評価したときのみ値の初期化が行われる点が異なります。(下記参照)

const と同様に、let はグローバル (一番上のスコープ) で宣言されたときに window オブジェクトのプロパティを生成しません

なぜ "let" という名前が選ばれたのかについては、こちら で解説されています。

スコープのルール

let で定義された変数は、自身が定義されたブロックと、そこに含まれるサブブロックがスコープになります。この点において let のふるまいは var にとてもよく似ています。大きな違いは、var で定義された変数のスコープはそれを含んでいる関数全体になるということです。

function varTest() {
  var x = 1;
  {
    var x = 2;  // 同じ変数です!
    console.log(x);  // 2
  }
  console.log(x);  // 2
}

function letTest() {
  let x = 1;
  {
    let x = 2;  // 異なる変数
    console.log(x);  // 2
  }
  console.log(x);  // 1
}

プログラムや関数の最上位においては、letvar とは異なり、グローバルオブジェクト上にプロパティを生成しません。

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

プライベートメンバーの模倣

コンストラクターの処理の中で let を使用すれば、クロージャを使用することなくプライベートメンバーを結び付けることができます。

var Thing;

{
  let privateScope = new WeakMap();
  let counter = 0;

  Thing = function() {
    this.someProperty = 'foo';

    privateScope.set(this, {
      hidden: ++counter,
    });
  };

  Thing.prototype.showPublic = function() {
    return this.someProperty;
  };

  Thing.prototype.showPrivate = function() {
    return privateScope.get(this).hidden;
  };
}

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

var thing = new Thing();

console.log(thing);
// Thing {someProperty: "foo"}

thing.showPublic();
// "foo"

thing.showPrivate();
// 1

ローカル変数をクロージャで閉じた場合と同様に、var を使ってプライバシーパターンを作成できますが、上の例のようなブロックスコープではなく、関数スコープ(通常はモジュールパターンの IIFE)が必要です。

再宣言

同じ関数やブロックのスコープ内で同じ変数を再宣言すると SyntaxError が発生します。

if (x) {
  let foo;
  let foo; // SyntaxError が発生します。
}

switch 文には 1 つのブロックしかないため、エラーを発生させてしまうかもしれません。

let x = 1;
switch(x) {
  case 0:
    let foo;
    break;

  case 1:
    let foo; // 再宣言のため TypeError
    break;
}

ただし、指摘しておくべき重要な点として、case 節の中で入れ子にしたブロックを使えば、新しいブロックスコープの字句環境を作ることができるため、上記のような再宣言エラーが発生しなくなります。

let x = 1;

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

一時的なデッドゾーン

var で宣言された変数が undefined の値で始まるのとは異なり、let の変数は定義が評価されるまで初期化されません。変数を宣言より前で参照すると ReferenceError が発生します。変数はブロックの先頭から初期化が行われるまで、「一時的なデッドゾーン」にあるのです。

function do_something() {
  console.log(bar); // undefined
  console.log(foo); // ReferenceError
  var bar = 1;
  let foo = 2;
}

一時的なデッドゾーンと typeof

単純に宣言されていない変数や undefined の値を持つ変数とは異なり、typeof 演算子を使用して一時的なデッドゾーン内の変数の型を確認するしようとすると、ReferenceError が発生します。

// 'undefined' を表示
console.log(typeof undeclaredVariable);

// 'ReferenceError' が発生します
console.log(typeof i);
let i = 10;

一時的なデッドゾーンとレキシカルスコープと組み合わせた例

字句スコープのため、式 (foo + 55) の中にある識別子 fooif ブロックの foo と評価され、その上にある変数 foo (33 の値を持つ) とは評価されません。

同じ行では、if ブロックの foo が字句環境よりすでに生成されていますが、初期化に達していない (完了していない) 状態です (その分自身の一部であるため)。

このブロックの foo は一時的なデッドゾーンの中にあります。

function test(){
   var foo = 33;
   if(foo) {
      let foo = (foo + 55); // ReferenceError
   }
}
test();

この現象は、以下のような状況で混乱を催すかもしれません。let n of n.a という命令は、すでに for ループブロックの私的スコープの中になります。そのため、識別子 n.a は命令自身 (let n) の最初の部分にある 'n' オブジェクトのプロパティ 'a' として解決されます。

その宣言文にはまだ到達・完了していないため、まだ一時的なデッドゾーン内にあるとみなされます。

function go(n) {
  // n here is defined!
  console.log(n); // Object {a: [1,2,3]}

  for (let n of n.a) { // ReferenceError
    console.log(n);
  }
}

go({a: [1, 2, 3]});

そのほかの場面

ブロックの中で使えば、let の変数のスコープはそのブロックの中に制限されます。スコープが自身の宣言された関数全体になる var との違いに注意してください。

var a = 1;
var b = 2;

if (a === 1) {
  var a = 11; // スコープはグローバル
  let b = 22; // スコープは if ブロック内

  console.log(a);  // 11
  console.log(b);  // 22
}

console.log(a); // 11
console.log(b); // 2

しかし、下記の varlet 宣言の組み合わせは、var がブロックの先頭に配置されているため、SyntaxError になります。これによって、変数が暗黙的に再宣言されるからです。

let x = 1;

{
  var x = 2; // 再宣言のため SyntaxError
}

仕様

仕様
ECMAScript (ECMA-262)
Let and Const Declarations の定義

ブラウザーの互換性

BCD tables only load in the browser

関連情報