JavaScript | ES6+ 文法:変数・宣言の進化 – let の仕様

JavaScript
スポンサーリンク

let とは何か

let は ES6 で導入された“ブロックスコープの再代入可能な変数宣言”です。ここが重要です:let は「宣言されたブロック内でのみ有効」「同じスコープで再宣言できない」「宣言前に使えない(Temporal Dead Zone)」という性質を持ちます。従来の var の“関数スコープ+巻き上げ(hoisting)による曖昧さ”を解消し、意図どおりに安全な変数管理ができます。

{
  let x = 1;
  console.log(x); // 1
}
console.log(typeof x); // "undefined"(ブロック外では存在しない)
JavaScript

スコープの違い(ブロックスコープ vs 関数スコープ)

let はブロックに閉じる

let は { } で囲まれたブロックに閉じます。if、for、try、switch などの中で宣言した let は、そのブロック外から見えません。ここが重要です:意図しない“外側の上書き”や“漏れ”を防げます。

if (true) {
  let a = 10;
}
// a はここでは未定義
JavaScript

var は関数に閉じる(比較用)

var は“関数スコープ”なので、ブロックをまたいで見えることがあり、意図せず値が共有されます。実務では let を標準にし、var は使わない方針が安全です。

if (true) {
  var v = 10;
}
console.log(v); // 10(ブロック外でも見える)←事故の元
JavaScript

Temporal Dead Zone(宣言前は使えない)

宣言前アクセスで例外

let は宣言位置まで変数が“存在しないゾーン”にあり、参照・代入ができません。この区間を TDZ(Temporal Dead Zone)と呼びます。ここが重要です:let を“宣言してから使う”癖をつけると、意図しない undefined 使用を防げます。

console.log(x); // ReferenceError(TDZ)
let x = 1;
JavaScript

デフォルト引数や内側ブロックでも TDZ

内側ブロックで外側の同名変数を“再宣言”すると、宣言前の行は TDZ になります。シャドーイングと TDZの組み合わせに注意しましょう。

let n = 5;
{
  // console.log(n); // ReferenceError(このブロック内で n を再宣言予定のため TDZ)
  let n = 10;
  console.log(n); // 10
}
JavaScript

再宣言不可・再代入可(const との対比)

同じスコープで再宣言はできない

let は同じスコープで同じ名前を再宣言すると SyntaxError になります。ここが重要です:重複宣言をコンパイル時に防げるので、保守性が上がります。

let a = 1;
// let a = 2; // SyntaxError(同スコープで再宣言不可)
JavaScript

値の再代入はできる(const は不可)

let は値を後から更新できます。const は“再代入不可”なので、基本は const を使い“変更が必要なときだけ let”を選ぶのが実務のベストプラクティスです。

let count = 0;
count += 1; // OK

const limit = 10;
// limit = 11; // TypeError(const は再代入不可)
JavaScript

ループとクロージャでの利点(初心者が躓くポイントの解決)

setTimeout とインデックスの捕捉が意図どおりに働く

var はループ後の最終値を共有しがちですが、let は“各反復ごとに新しい束縛”になり、期待どおりの値がクロージャに閉じ込められます。ここが重要です:非同期処理でインデックスを正しく扱えることが、let の最大の恩恵のひとつです。

for (let i = 1; i <= 3; i++) {
  setTimeout(() => console.log(i), 0); // 1, 2, 3 と順に出る
}

// var だと全て 4 になってしまう典型的事故
for (var j = 1; j <= 3; j++) {
  setTimeout(() => console.log(j), 0); // 4, 4, 4
}
JavaScript

for 文ヘッダの let は“ブロック限り”

for (let i = 0; i < 3; i++) の i はループブロックに限定され、外から見えません。意図しないスコープ汚染が防げます。

for (let i = 0; i < 2; i++) {}
// console.log(i); // ReferenceError(スコープ外)
JavaScript

グローバルと let(安全な名前空間)

グローバルオブジェクトにぶら下がらない

ブラウザの var グローバルは window にプロパティとして現れますが、let のグローバルは window に現れません。ここが重要です:名前衝突を避け、安全にグローバルスコープを使えます(とはいえ、グローバル乱用は避けるべき)。

var gv = 1;
let gl = 2;

console.log(window.gv); // 1
console.log(window.gl); // undefined(let はぶら下がらない)
JavaScript

switch 文の落とし穴と対策

switch は単一ブロック扱い(case ごとの let 再宣言は不可)

switch は全体が一つのブロックです。同名の let を複数の case で宣言すると再宣言エラーになります。ここが重要です:case ごとに { } でブロックを作り、宣言を分けるのが安全です。

switch (type) {
  case "A": {
    let x = 1;
    console.log(x);
    break;
  }
  case "B": {
    let x = 2; // 別ブロックなので OK
    console.log(x);
    break;
  }
}
JavaScript

実務での使い分け(指針)

原則は const、必要なときだけ let

不変が正義です。まず const を試し、“後で変える必要がある”と分かったら let にする。ここが重要です:不変にするとバグが減り、意図が明確になります。let は「カウンタ」「集計の累積」「一時変数の更新」に限定すると読みやすくなります。

const items = [1, 2, 3];     // 変わらない一覧
let sum = 0;                  // 累積は let
for (const n of items) sum += n;
JavaScript

宣言は最小スコープに置く

必要な場所で宣言し、不要な広いスコープに漏らさない。TDZ と合わせて「宣言してから使う」を徹底すると、未初期化の事故を避けられます。

function f() {
  // 使う直前で宣言・初期化
  let tmp = compute();
  return tmp * 2;
}
JavaScript

例題で理解を固める

let のシャドーイングと外側の保護

内側で同名を宣言しても外側は守られます。ここが重要です:意図的に“局所的な上書き”がしたいとき、let で安全に閉じ込められます。

let label = "outer";
{
  let label = "inner";
  console.log(label); // "inner"
}
console.log(label); // "outer"
JavaScript

TDZ がバグを先に見つける例

未宣言のまま使ってしまうコードを、その場で止めます。“静かな undefined”より安全です。

function calc() {
  // console.log(total); // ReferenceError(TDZ)
  let total = 0;
  return total;
}
JavaScript

まとめ

let の核心は「ブロックスコープ」「宣言前不可(TDZ)」「再宣言不可・再代入可」です。これにより、従来の var の曖昧さ(巻き上げ・スコープ漏れ・クロージャ事故)を解消できます。実務では“まず const、必要なら let”を原則にし、宣言は最小スコープへ、switch は case をブロックで囲む。非同期やループのインデックス捕捉で let を使うと、期待どおりに動き、初心者でもバグを避けながら読みやすいコードを書けます。

タイトルとURLをコピーしました