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 の可能性)
JavaScriptswitch の単一ブロック問題
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+ の変数管理を安心・正確に扱えます。
