letを使った変数宣言とスコープの管理をわかりやすく解説

スポンサーリンク
スポンサーリンク
amazon
スマイルSALE
--:--:--
ad. 価格範囲を指定して商品を探せます

letの概要

ブロックスコープ変数の宣言 JavaScript予約語

let

概要 let(レット)は、JavaScriptでデータを一時的に記憶するための「変数(へんすう)」を新しく作成(宣言)する、現代のJavaScript開発における最標準のキーワードです。
2015年のECMAScript(ES6)という大規模なアップデートの際に、それまでの古いキーワードであった var(ヴァー)が抱えていた数々の危険な仕様やバグの温床を根本から解決し、安全なコードを書くために導入されました。

わかりやすく説明 letは、中身の詰め替え(再代入)が自由にできる、「有効期限付きの便利なデータ収納箱」です。
波カッコ {} で囲まれたエリアの中だけでしか存在できないという厳格なルール(ブロックスコープ)を持っているため、「他の場所にある同名の変数とごちゃ混ぜになってデータが上書きされてしまった」という、プログラミング初心者が最も陥りやすいトラブルを未然に防いでくれます。

  • 波カッコ {}(if文、for文、関数など)の内側で let を使って作った変数は、そのカッコの外側に出た瞬間に自動的に消滅するため、メモリの節約と安全性の両立が可能です。
  • 同じ名前の箱を同じスコープ内で二重に作る「再宣言」はシステムが未然にエラーとしてブロックしてくれますが、箱の中身を最新のデータにアップデートする「再代入」はいつでも自由に行えます。
  • プログラムの実行時に「変数の中身が準備されるまでの立ち入り禁止ゾーン(TDZ)」を作り出す仕様があり、宣言より前の行でフライングして変数を使おうとするミスを厳格に指摘してくれます。

letの基本的な使い方

let 変数名 = 値; という形式で記述し、データを格納する箱を用意します。
一度作成した変数に対して、let を付けずに 変数名 = 新しい値; と書くことで中身を何度でも書き換えることができますが、同じ場所で「もう一度 let を使って同じ名前の箱を作る」ことは禁止されています。

// letを使った変数宣言と、値の代入
let x = 10; // 変数xという箱を作り、初期値として10を入れます
console.log(x); // 出力: 10

// 1. 再代入の実験(中身の書き換えはいつでもOK)
x = 20; // 既存の箱xの中身を20にアップデートします
console.log(x); // 出力: 20

// 2. 再宣言の実験(同じ名前の箱を同じ部屋に作ろうとすると...)
// let x = 30; // 🛑 構文エラー:Identifier 'x' has already been declared
  • 再代入ができる柔軟性を持っているため、ゲームのスコアの加算や、ループ処理のカウンターなど、プログラムの進行に応じて数値がコロコロ変化するシーンで大活躍します。
  • 同じスコープ内での再宣言をエラーにしてくれるおかげで、数千行に及ぶ巨大なコードをチームで開発していても、知らずに変数を重複させてシステムを破壊するリスクがゼロになります。

ブロックスコープとlet(有効範囲の厳格化)

let の最大の特徴は、波カッコ {} で囲まれた空間(ブロック)をひとつの「独立した隔離部屋」として認識する ブロックスコープ にあります。
部屋の中で生まれた変数は、その部屋の中でしか使えず、部屋の外からは影も形も見えなくなります。
これは if文の条件分岐や、for文のループ処理のカッコ内でも強力に適用されます。

// 波カッコによる隔離空間の作成
{
    let blockScoped = "この変数はブロック内でのみ有効";
    console.log(blockScoped); // ⭕ 部屋の中なので、問題なく文字が出力されます
}
// 部屋の外に出てから、中の変数を呼び出そうとすると...
// console.log(blockScoped); // 🛑 参照エラー:blockScoped is not defined(そんな変数は存在しません)

// 実践例:for文(ループ処理)内でのスコープの保護
for (let i = 0; i < 3; i++) {
    console.log(i); // ⭕ ループ内なので 0, 1, 2 と順番に出力されます
}
// ループがすべて終了した後、カウンター変数 i を外から覗こうとすると...
// console.log(i); // 🛑 参照エラー:i is not defined (iはループ終了と同時にこの世から消滅しています)
  • 昔の var の場合は、iffor の波カッコを突き破って外側まで変数が漏れ出してしまい、無関係な場所の処理をバグらせる原因になっていましたが、let の登場によってその心配は完全に過去のものとなりました。
  • 外側の広い世界で定義された変数は、内側の隔離部屋からでも自由に見ることができます(親の部屋にある家具は、子の部屋からでも触れるイメージです)。

Temporal Dead Zone(TDZ / 一時的死区)の仕組み

JavaScriptには、コードを実行する前にすべての変数宣言をスコープの先頭に登録する「巻き上げ(Hoisting:ホイスティング)」という独自のエンジン特性があります。
古い var の時は、宣言より前の行で変数にアクセスしてもエラーにならず undefined(中身が空っぽ)という生ぬるい状態で動き続けてしまい、発見の難しいバグになりました。
これに対し let は、巻き上げ自体はされますが、実際の宣言文が書かれている行に処理が到達するまでは「立ち入り禁止ゾーン(TDZ:Temporal Dead Zone)」としてその変数を厳重にロックし、フライングアクセスを徹底的に叩き落とす仕様になっています。

// ------ ここから TDZ(立ち入り禁止ゾーン)がスタート! ------
// console.log(myVar); // 🛑 参照エラー:Cannot access 'myVar' before initialization(初期化前にアクセスできません)
// -----------------------------------------------------------

let myVar = "初期化された変数"; // ⭕ ここで宣言文に到達! ロックが解除され、データが箱に詰まります

console.log(myVar); // ⭕ ロック解除後なので、安全に「初期化された変数」と出力されます
  • 「変数は、必ず先に宣言を済ませてから、下の行で使う」という、人間にとって当たり前で自然な思考通りのコードを書かせるための、JavaScript側の親切な安全ガードレールシステムがTDZです。

注意点

  • 再代入はOK、再宣言はNGの区別: let score = 10; score = 20;(再代入)は全く問題ありませんが、let score = 10; let score = 20;(再宣言)は即座にエラーになります。この2つの言葉の定義の違いをしっかりと整理しておきましょう。
  • varの完全な封印: 現代のフロントエンド開発において、昔の var キーワードを使用するメリットは1ミリもありません。
    既存の古いシステムをメンテナンスする時を除き、新しくコードを書く際は let、もしくは次に解説する const だけを使用することを徹底してください。
  • グローバルオブジェクトに登録されない: ブラウザ環境の最上位に位置する window オブジェクトにおいて、var x = 1; と書くと window.x としてどこからでもアクセスできるようになってしまいましたが、let x = 1; で宣言した変数は window の中身を汚さないため、Webページの他のスクリプトとの競合を安全に防げます。

よくある質問

Q: varlet の違いについて、一目でわかるように表などでスッキリ整理して教えてほしいです。
A: 最大の違いである「スコープ(有効範囲)」「再宣言」「巻き上げ時の挙動」の3点を比較表にまとめました。let がいかに安全に設計されているかが分かります。

機能・特徴 古い var 現代の let
有効スコープ 関数スコープ(ifやforを貫通する) ブロックスコープ( {} の中だけ)
同じ部屋での再宣言 何度でも可能(値が勝手に上書きされる) 不可能(エラーでシステムが守られる)
フライングアクセス undefined になり、動いてしまう TDZにより参照エラー(即座に停止)
Q: letconst はどちらもブロックスコープを持っていて安全ですが、実務ではどちらを優先して使うべきですか?
A: 実務における鉄則は、「まずは const を第一選択にし、どうしても再代入が必要な場合(カウンターなど)にだけ let を使う」 というアプローチです。
プログラムに出てくる変数のうち、実は約8割〜9割は一度代入したら中身を変えないデータです。
そのため、基本をすべて const(再代入すら不可能な超ガチガチの箱)にしておき、合計値の計算やフラグの切り替えといった「後から中身が変化することが確定している変数」にだけピンポイントで let を指定することで、コードの意図が他人に伝わりやすくなり、バグも最小限に抑えられます。
Q: 関数の中で let x = 1; と書き、その外側でも let x = 100; と書いた場合、名前が被っていますがエラーになりますか?
A: いいえ、エラーにはならず、それぞれが「別々の独立した変数」として安全に共存します。
このように、外側(グローバルなど)の広いスコープにある変数と同じ名前の変数を、内側の狭いスコープ(関数内など)で新しく宣言すると、内側の世界では内側の変数が最優先で呼び出されるようになります。
この現象をプログラミング用語で 「シャドーイング(Shadowing)」 と呼びます(外側の変数が、内側の変数の影に隠れて見えなくなるイメージです)。

// シャドーイングの実験
let target = "外側の広い世界のデータ";

{
    // 名前は同じですが、隔離部屋の中で新しく宣言したため別物扱いになります
    let target = "内側の隔離部屋のデータ"; 
    console.log(target); // ⭕ 出力: "内側の隔離部屋のデータ" (内側が優先されます)
}

console.log(target); // ⭕ 出力: "外側の広い世界のデータ" (外側の箱は汚されずに無傷で残っています)

まとめ

letは、古くからあるJavaScriptのルーズで危険だった変数管理の歴史に終止符を打ち、モダンで堅牢なWebアプリケーションを開発するために作られた必須の最重要キーワードです。

  • 波カッコ {} の境界線を意識し、その変数が「どこからどこまでの範囲で生きているのか(ブロックスコープ)」を常に頭の中に思い描きながらコードを配置しましょう。
  • 再代入が必要なシーン(状態が変化するデータ)では let を使い、値が変わらない固定のデータには const を使うというメリハリのある使い分けの習慣を身につけてください。
  • 巻き上げによるフライング使用を厳しく防ぐTDZの特性を理解し、変数宣言を常に処理の先頭部分にまとめて記述する、綺麗で安全なプログラミングスタイルをマスターしていきましょう。