ES6で導入されたブロックスコープ({}内)は、変数宣言の振る舞いを根本的に変革しました。varと新規宣言子の本質的違いを実例で解説します。
スコープ特性の比較
varは関数スコープを持ち、変数の巻き上げ(hoisting)が発生します。一方let/constはブロックスコープ限定で、宣言前の参照は禁止されています。グローバルスコープでの振る舞いも異なります:
var x = 100;
console.log(window.x); // 100(windowプロパティに登録)
let y = 200;
console.log(window.y); // undefined(windowに登録されない)
クロージャ実装の差異
ループ内関数の挙動はスコープ特性を如実に示します。varを使用すると予期せぬ結果に:
const funcs = [];
for (var idx = 0; idx < 3; idx++) {
funcs[idx] = () => console.log(idx);
}
funcs[0](); // 3(最終値が参照される)
letでは各イテレーションで独立したスコープが生成されるため、期待通りの動作:
const handlers = [];
for (let counter = 0; counter < 3; counter++) {
handlers[counter] = () => console.log(counter);
}
handlers[1](); // 1(各counter値が保持される)
宣言子の実践的制約
1. varの特性
- 変数巻き上げにより宣言前参照がundefined:
console.log(value); // undefined
var value = 50;
- ブロック外へのスコープ漏れ:
for (var i = 0; i < 2; i++) {}
console.log(i); // 2(参照可能)
2. letの制約
- ブロックスコープ外での参照エラー:
{
let secret = "hidden";
}
console.log(secret); // ReferenceError
- 一時的デッドゾーン(TDZ):
console.log(active); // ReferenceError
let active = true;
3. constの不変性
再代入禁止ですが、オブジェクトのプロパティ変更は許可されます:
const config = { port: 8080 };
config.port = 3000; // 有効(参照先オブジェクトの変更)
config = { host: "localhost" }; // TypeError(再代入禁止)
// 完全な不変化が必要な場合
Object.freeze(config);
スコープチェーンの深層
TDZは宣言位置までのスコープ領域で発生します。以下のコードはTDZの具体例:
let status = "initial";
if (true) {
// statusへの参照はTDZでエラー
status = "updated";
let status = "ready";
}
ブロックスコープの理解は、非同期処理やモジュール設計において特に重要です。ループカウンタや一時変数にはlet、再代入不要な設定値にはconstを優先的に使用すべきです。