let
Baseline
Widely available
This feature is well established and works across many devices and browser versions. It’s been available across browsers since 2016年9月.
let 宣言は、再代入可能な、ブロックスコープのローカル変数を宣言します。任意で値を代入して初期化できます。
試してみましょう
let x = 1;
if (x === 1) {
let x = 2;
console.log(x);
// 予想される結果: 2
}
console.log(x);
// 予想される結果: 1
構文
let name1;
let name1 = value1;
let name1 = value1, name2 = value2;
let name1, name2 = value2;
let name1 = value1, name2, /* …, */ nameN = valueN;
引数
nameN-
宣言する変数または複数の変数の名前です。それぞれは JavaScript の正規の識別子または構造分解パターンである必要があります。
valueN省略可-
変数の初期値です。任意の正規の式にすることができます。デフォルト値は
undefinedです。
解説
let で宣言された変数のスコープは、その let 宣言を直近の中括弧で囲む、次のいずれかの構文です。
- ブロック 文
switch文try...catch文- いずれかの
for文において、letが文のヘッダーにある場合には、ループの本体 - 関数本体
- 静的初期化ブロック
上記のいずれもない場合は、次のようになります。
- モジュールモードで実行中のコードで場合は、現在のモジュール
- スクリプトモードで実行中のコードでは、グローバルスコープ
var と比較して、let 宣言には次のような違いがあります。
-
let宣言は関数ではなく、ブロックのスコープになります。 -
let宣言は、宣言の場所に到達した後にのみアクセスできます(一時的なデッドゾーンを参照)。このため、let宣言は一般に巻き上げされないと考えられています。 -
let宣言は、スクリプトの最上位で宣言された場合にglobalThisのプロパティを作成しません。 -
let宣言は、同じスコープ内では他のどの宣言においても再宣言できません。 -
letは文ではなく宣言を始めます。それは、ブロックの本体として単独のlet宣言を使用することはできないということです(変数にアクセスする方法がないため、当然です)。jsif (true) let a = 1; // SyntaxError: Lexical declaration cannot appear in a single-statement context
なお let は、厳格モードでない場合、var または function で宣言する際に識別子名として使用することができますが、予期しない構文場の曖昧さを避けるため、let を識別子名として使うのは避けてください。
多くのスタイルガイド(MDN を含む)では、変数がそのスコープ内で再代入されない場合、let よりも const を使用することを推奨しています。これにより、変数の型(場合によっては値)が不変であることが明確になります。変更可能な非プリミティブ型には let を推奨している場合もあります。
let キーワードに続くリストはバインドリストと呼ばれ、カンマで区切られます。ここでカンマはカンマ演算子ではなく、= 記号は代入演算子ではありません。後続の変数の初期化子は、リスト内の先行する変数を参照できます。
一時的なデッドゾーン (TDZ)
let、const、class で宣言された変数は、ブロックの始まりからコードが実行されて変数が宣言され初期化される行に到達するまでは、「一時的なデッドゾーン」(TDZ) 内にあると言います。
TDZ の中にいる間は、変数は値で初期化されておらず、アクセスしようとすると ReferenceError が発生します。変数は、宣言されたコードの行まで実行されると、値で初期化されます。変数宣言で初期値を指定しなかった場合は、undefined という値で初期化されます。
これは var で宣言された変数が宣言前にアクセスされた場合に undefined の値を返すのとは異なります。以下のコードは、let と var が宣言された行より前のコードでアクセスされた場合に、異なる結果が得られることを示しています。
{
// TDZ はスコープの先頭で始まる
console.log(bar); // "undefined"
console.log(foo); // ReferenceError: Cannot access 'foo' before initialization
var bar = 1;
let foo = 2; // TDZ の終了(foo について)
}
「一時的」という言葉が使われているのは、このゾーンがコードが書かれた場所(位置)ではなく、実行の順序(時間)に依存するゾーンであるためです。例えば、下記のコードは、let 変数を使用する関数は変数宣言の前に現れていますが、その関数が TDZ の外で呼び出されているため、動作します。
{
// TDZ はスコープの先頭から始まる
const func = () => console.log(letVar); // OK
// TDZ の中では、letVar にアクセスすると `ReferenceError` が発生する
let letVar = 3; // TDZ の終わり(letVar について)
func(); // TDZ の外での呼び出し
}
typeof 演算子を使用して一時的なデッドゾーン内の変数の型を確認するしようとすると、ReferenceError が発生します。
{
typeof i; // ReferenceError: Cannot access 'i' before initialization
let i = 10;
}
これは、宣言されていない変数に対して typeof を使用した場合、undefined の値と見なされるのとは異なります。
console.log(typeof undeclaredVariable); // "undefined"
メモ:
let および const 宣言は、現在のスクリプトが処理される際にのみ処理されます。1 つの HTML 内でスクリプトモードで実行される 2 つの <script> 要素がある場合、1 つ目のスクリプトは、2 つ目スクリプトで宣言された最上位の let または const 変数に対する TDZ 制限の対象外です。ただし、1 つ目のスクリプトで let または const 変数を宣言した場合、2 つ目スクリプトで再度宣言すると再宣言エラーが発生します。
再宣言
let 宣言は、(訳注:同じ名前の)他の宣言、例えば let, const, class, function, var, import 宣言と同じスコープに置くことはできません。
{
let foo;
let foo; // SyntaxError: Identifier 'foo' has already been declared
}
関数本体内の let 宣言は、引数と同じ名前を持つことはできません。catch ブロック内の let 宣言は、catch でバインドされた識別子と同じ名前を持つことはできません。
function foo(a) {
let a = 1; // SyntaxError: Identifier 'a' has already been declared
}
try {
} catch (e) {
let e; // SyntaxError: Identifier 'e' has already been declared
}
Firefox のウェブコンソール(ツール > ウェブ開発者ツール > ウェブコンソール)などの REPL で実験しているときに、同じ名前の let 宣言を 2 つの入力で実行すると、同じ再宣言エラーが得られる場合があります。この課題については、Firefox bug 1580891でさらなる議論をご覧ください。Chrome コンソールでは、異なる REPL 入力間で let を再宣言することができます。
switch 文には 1 つのブロックしかないため、エラーが発生してしまうかもしれません。
let x = 1;
switch (x) {
case 0:
let foo;
break;
case 1:
let foo; // SyntaxError: Identifier 'foo' has already been declared
break;
}
エラーを避けるためには、それぞれの case を新しいブロック文で囲んでください。
let x = 1;
switch (x) {
case 0: {
let foo;
break;
}
case 1: {
let foo;
break;
}
}
例
>スコープのルール
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
一時的なデッドゾーンと字句スコープと組み合わせた例
このブロックの foo は一時的なデッドゾーンの中にあります。
function test() {
var foo = 33;
if (foo) {
let foo = foo + 55; // ReferenceError
}
}
test();
外側の var foo に値があるため、if ブロックは実行されます。しかし、字句スコープにより、この値はブロックの内部では利用できません。if ブロックの内部にある識別子 foo は let foo になります。式 foo + 55 で ReferenceError が発生します。これは let foo の初期化がまだ完了していない、つまり一時的なデッドゾーンにあるためです。
この現象は、以下のような状況で紛らわしくなることがあります。let n of n.a という命令は、すでに for...of ループのブロックのプライベートスコープ内にあります。そのため、識別子 n.a は、命令自体の最初の部分 (let n) にある 'n' オブジェクトのプロパティ a として解決されます。その宣言文にはまだ到達・完了していないため、まだ一時的なデッドゾーン内にあるとみなされます。
function go(n) {
// n はここでは定義されていない
console.log(n); // { 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;
{
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
}
構造分解による宣言
それぞれの = の左側もバインドパターンである可能性があります。このことは、複数の変数を一度に作成することができるということです。
const result = /(a+)(b+)(c+)/.exec("aaabcc");
let [, a, b, c] = result;
console.log(a, b, c); // "aaa" "b" "cc"
詳しい情報は構造分解を参照してください。
仕様書
| Specification |
|---|
| ECMAScript® 2026 Language Specification> # sec-let-and-const-declarations> |
ブラウザーの互換性
関連情報
varconst- 巻き上げ
- ES6 In Depth:
letandconst- hacks.mozilla.org (2015) - Breaking changes in
letandconstin Firefox 44 - blog.mozilla.org (2015) - You Don't Know JS: Scope & Closures, Ch.3: Function vs. Block Scope - Kyle Simpson
- What is the Temporal Dead Zone? - Stack Overflow
- What is the difference between using
letandvar? - Stack Overflow - Why was the name 'let' chosen for block-scoped variable declarations in JavaScript? - Stack Overflow