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 キーワードのように、変数をブロックスコープに関係なく、グローバルや関数全体のローカルに定義するようなことはありません。他にも、var と let は、後者はパーサーが評価したときのみ値の初期化が行われる点が異なります。(下記参照)
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
}
プログラムや関数の最上位においては、let は var とは異なり、グローバルオブジェクト上にプロパティを生成しません。
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) の中にある識別子 foo は if ブロックの 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
しかし、下記の var と let 宣言の組み合わせは、var がブロックの先頭に配置されているため、SyntaxError になります。これによって、変数が暗黙的に再宣言されるからです。
let x = 1;
{
var x = 2; // 再宣言のため SyntaxError
}
仕様
ブラウザーの互換性
BCD tables only load in the browser

