JavaScript | ES6+ 文法:変数・宣言の進化 – TDZ(Temporal Dead Zone)

JavaScript
スポンサーリンク

TDZ(Temporal Dead Zone)とは何か

TDZ は「let/const で宣言される変数が、宣言位置まで“存在しない扱い”になるゾーン」のことです。ここが重要です:TDZ 中にその変数へアクセスすると ReferenceError になり、未初期化の値(undefined)をうっかり使うバグを事前に防いでくれます。対象は let と const(そしてクラス宣言)で、var には TDZ はありません。

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

なぜ TDZ があるのか(安全性のための設計)

TDZ は「宣言前に使ってしまう」典型的なバグを止めるために導入されました。ここが重要です:ES5 の var は巻き上げ(hoisting)で宣言前でも undefined になり、静かに処理が進んでバグが隠れがちでした。ES6 の let/const は“宣言してから使う”を強制し、意図どおりの初期化順を守らせます。


基本例(宣言前アクセスは常にエラー)

let/const の宣言前アクセス

宣言より前の行で参照・代入・typeof すら禁止されます(typeof でも ReferenceError)。

// typeof でもだめ
typeof n;  // ReferenceError(n は TDZ)
let n = 10;

// const も同様
value;     // ReferenceError
const value = 42;
JavaScript

ここが重要です:TDZ は「宣言位置に到達するまで」の範囲。到達後は通常どおり使えます。


シャドーイングによる TDZ(内側ブロックでの再宣言)

同名変数を内側で宣言すると外側が“見えなくなる”

内側ブロックで同名の let/const を宣言する予定があると、その宣言より前の行で同名へのアクセスは TDZ になります。

let label = "outer";
{
  // console.log(label); // ReferenceError(このブロックで label を宣言予定のため TDZ)
  let label = "inner";
  console.log(label); // "inner"
}
console.log(label); // "outer"
JavaScript

ここが重要です:紛らわしい同名を避ける、または宣言を先に書いて TDZ 区間を最短にするのが安全です。


関数のデフォルト引数でも TDZ が起こる

引数の初期化順と参照のタイミング

デフォルト引数の評価時に、まだ初期化されていない同名の let/const を参照すると TDZ になります。

let rate = 0.1;
function priceWithTax(amount, r = rate) { // OK(外側 rate を参照)
  return Math.round(amount * (1 + r));
}

function f(x = y) { // NG(y はまだ宣言前)
  return x;
}
let y = 5;
// f(); // ReferenceError(y は TDZ 中)
JavaScript

ここが重要です:デフォルト引数で参照する値は、必ずその時点で初期化済みであることを確認する(宣言順を守る)。


クラス宣言にも TDZ がある

クラスは let/const と同様に“宣言してから使う”

クラス宣言は宣言前アクセスで TDZ に入ります。関数宣言と違い、クラスは巻き上げられません。

new Person(); // ReferenceError(TDZ)
class Person {
  constructor() {}
}
JavaScript

ここが重要です:クラスを使うときは、必ず宣言後にインスタンス化や参照を行う。


TDZ を避けるための書き方(安全な初期化順)

宣言は“使う直前”に置く

宣言より前の行で参照しないよう、必要な場所で宣言・初期化します。TDZ は設計でほぼ回避できます。

function computeTotal(items) {
  const rate = 0.1;            // 先に宣言・初期化
  let total = 0;               // 可変値は let
  for (const it of items) {
    const price = it.price ?? 0;
    total += Math.round(price * (1 + rate));
  }
  return total;
}
JavaScript

ブロックを使って“局所化”

一時的な値は小さなブロックに閉じ込め、外へ漏らさない。宣言順が自然に守られ、TDZ の影響範囲も最小化できます。

{
  const key = query.trim().toLowerCase();
  result = rows.filter(r => r.name.toLowerCase().includes(key));
}
// ここでは key にアクセスしない(そもそも見えない)
JavaScript

よくある落とし穴と対策(重要ポイントの深掘り)

typeof の誤解

var では未宣言でも typeof x が “undefined” でしたが、let/const の TDZ 中は typeof でも ReferenceError になります。未宣言・未初期化の検出に typeof を使う癖は捨てる。

// let/const 対象では typeof も安全ではない
// typeof x // ReferenceError(x が TDZ の可能性)
JavaScript

switch の単一ブロック問題

switch 全体が一つのブロックなので、複数 case で同名 let/const を宣言すると再宣言エラーや TDZ が絡みます。case を { } で囲み、宣言を分ける。

switch (kind) {
  case "A": {
    const x = 1; console.log(x); break;
  }
  case "B": {
    const x = 2; console.log(x); break;
  }
}
JavaScript

影響範囲の縮小

同名の再宣言や宣言前参照が起きるのは“広いスコープ”で変数が長生きしている時に多い。スコープを最小化し、名前を具体的にして衝突を避ける。


例題で理解を固める

TDZ がバグを早期に炙り出す

宣言順が間違っていると、実行時に即エラーで気づける。静かな undefined より圧倒的に安全です。

function buildUrl(page) {
  // console.log(base); // ReferenceError(TDZ)
  const base = "https://example.com";
  return `${base}?page=${page}`;
}
JavaScript

シャドーイングと TDZ のセット動作

内側で同名を宣言するなら、先に宣言してから使う。宣言前参照は避ける。

let value = "outer";
{
  let value = "inner";      // 先に宣言
  console.log(value);       // OK
}
JavaScript

まとめ

TDZ の核心は「let/const(とクラス)は宣言位置まで存在しない扱いで、宣言前アクセスは ReferenceError」だということです。これにより、未初期化や宣言順ミスを“その場で”検出できます。安全に書くコツは、宣言は使う直前に、スコープは最小に、同名の再宣言を避けるか先に宣言する。typeof に頼らず、初期化順を意識する。TDZ を理解すれば、初心者でも ES6+ の変数管理を安心・正確に扱えます。

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