let

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

構文

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

引数

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

解説

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 の定義

ブラウザーの互換性

Update compatibility data on GitHub
デスクトップモバイルサーバー
ChromeEdgeFirefoxInternet ExplorerOperaSafariAndroid webviewAndroid 版 ChromeAndroid 版 FirefoxAndroid 版 OperaiOSのSafariSamsung InternetNode.js
letChrome 完全対応 49
完全対応 49
未対応 48 — 49
補足 無効
補足 Support outside of strict mode.
無効 From version 48 until version 49 (exclusive): this feature is behind the Enable Experimental JavaScript Features preference. To change preferences in Chrome, visit chrome://flags.
未対応 41 — 49
補足
補足 Strict mode is required.
Edge 完全対応 14
完全対応 14
未対応 12 — 14
補足
補足 In Edge 12 and 13, let within a for loop initializer does not create a separate variable for each loop iteration as defined by ES2015. Instead, it behaves as though the loop were wrapped in a scoping block with the let immediately before the loop.
Firefox 完全対応 44
補足
完全対応 44
補足
補足 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).
補足 Prior to Firefox 46, a TypeError is thrown on redeclaration instead of a SyntaxError.
補足 Firefox 54 adds support of let in workers.
IE 部分対応 11
補足
部分対応 11
補足
補足 In Internet Explorer, let within a for loop initializer does not create a separate variable for each loop iteration as defined by ES2015. Instead, it behaves as though the loop were wrapped in a scoping block with the let immediately before the loop.
Opera 完全対応 17Safari 完全対応 10WebView Android 完全対応 49
完全対応 49
未対応 41 — 49
補足
補足 Strict mode is required.
Chrome Android 完全対応 49
完全対応 49
未対応 48 — 49
補足 無効
補足 Support outside of strict mode.
無効 From version 48 until version 49 (exclusive): this feature is behind the Enable Experimental JavaScript Features preference. To change preferences in Chrome, visit chrome://flags.
未対応 41 — 49
補足
補足 Strict mode is required.
Firefox Android 完全対応 44
補足
完全対応 44
補足
補足 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).
補足 Prior to Firefox 46, a TypeError is thrown on redeclaration instead of a SyntaxError.
補足 Firefox 54 adds support of let in workers.
Opera Android 完全対応 18Safari iOS 完全対応 10Samsung Internet Android 完全対応 5.0
完全対応 5.0
未対応 4.0 — 5.0
補足
補足 Strict mode is required.
nodejs 完全対応 6.0.0

凡例

完全対応  
完全対応
部分対応  
部分対応
実装ノートを参照してください。
実装ノートを参照してください。
ユーザーが明示的にこの機能を有効にしなければなりません。
ユーザーが明示的にこの機能を有効にしなければなりません。

関連情報