let

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

構文

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

引数

var1, var2, …, varN
変数の名前。任意の有効な識別子を指定できます。
value1, value2, …, valueN Optional
変数の初期値。任意の有効な式を指定できます。

説明

let を使用することで、変数のスコープをそれが使用されたブロック、文または式に限定することができます。これは var キーワードとは異なり、グローバル変数を定義したり、ブロックスコープに留まらない関数全体でのローカル変数を定義したりしません。その他の varlet の違いは後者がパーサーが評価したときにのみ value に初期化されます(下記参照)

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

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

スコーピングのルール

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

function varTest() {
  var x = 31;
  if (true) {
    var x = 71;  // 同じ変数です!
    console.log(x);  // 71
  }
  console.log(x);  // 71
}

function letTest() {
  let x = 31;
  if (true) {
    let x = 71;  // 異なる変数
    console.log(x);  // 71
  }
  console.log(x);  // 31
}

var とは異なり、プログラムのトップレベルと関数で、グローバルオブジェクト上にプロパティを生成しません。たとえば:

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

プライベートメンバーのエミュレート

constructor で処理することで、クロージャを使用することなくプライベートメンバーを生成するために 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;
  }
}

Temporal dead zone

undefined の値で始まる var 変数と異なり、 let 変数は定義が評価されるまで初期化されません。変数を宣言より前で参照することは ReferenceError を引き起こします。ブロックの始めから変数宣言が実行されるまで、変数は "temporal dead zone" の中にいるのです。

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

Temporal dead zone と typeof

単純に宣言されていない変数や undefined の値を保持する変数とは異なり、 TDZ内の変数の型を確認するために typeof 演算子を使用すると、ReferenceError がスローされます:

// 'undefined' が表示されます
console.log(typeof undeclaredVariable);
// 'ReferenceError' が発生します
console.log(typeof i);
let i = 10;

レキシカルスコープと組み合わせた Temporal dead zone の例

レキシカルスコープにより、式 (foo + 55) 内の識別子 "foo" は、if ブロックの foo に評価され、その上にある 33 の値を持つ変数 foo には評価されません
その行では、if ブロックの "foo" はすでにレキシカル環境で作成されていますが、未だ(ステートメントそのものの一部である)その初期化に到達・終了していないので、その foo はまだ Temporal dead zone 内にあります。

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

この現象は、次のような状況であなたを混乱させる可能性があります。let n of n.a 命令はfor ループブロックのプライベートスコープにすでに存在し、したがって、識別子 "n.a" は、命令自体の最初の部分にある 'n' オブジェクト ("let n") のプロパティ 'a' に解決されます。その宣言ステートメントに到達・終了していないので、その n 及び n.a はまだ Temporal dead zone 内にあります。

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;

if (true) {
  var x = 2; // 再宣言による SyntaxError
}

仕様

仕様 ステータス コメント
ECMAScript 2015 (6th Edition, ECMA-262)
Let and Const Declarations の定義
標準 初期定義。let 式や let ブロックは定義されていない。
ECMAScript Latest Draft (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

凡例

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

関連項目