エンジニア諸君。コードベースの墓場をいくつも見てきた経験から言わせてもらうと、バグの9割は「JavaScriptの挙動を理解したつもり」になっている傲慢さから生まれる。
今日は、多くのエンジニアが「なんとなく」で済ませている `var` と `let/const` の深淵、特に「巻き上げ(Hoisting)」と「初期化タイミング」の仕様上の断絶について解剖しよう。
—
1. 巻き上げの正体:生成フェーズという「空白」
多くの解説記事では `var` は `undefined` に初期化され、`let/const` はそうではない、と説明される。だが、ブラウザのV8エンジンやJavaScriptCoreが裏で何をしているのかを知る必要がある。
JavaScriptの実行は、大きく「作成(Creation)フェーズ」と「実行(Execution)フェーズ」に分かれる。
- `var` の場合: 作成フェーズでメモリ上の環境レコード(Environment Record)に名前が登録され、即座に `undefined` で初期化される。だから、宣言前でも「型は存在するが値は空」という、極めて危険な状態で参照できてしまうのだ。
- `let/const` の場合: 作成フェーズでメモリに名前は登録されるが、「未初期化(Uninitialized)」という状態のまま凍結される。これがかの有名な「一時的死域(Temporal Dead Zone: TDZ)」だ。
// TDZの実験
console.log(a); // ReferenceError: Cannot access ‘a’ before initialization
let a = 10;
// なぜこれが重要か?
// もしTDZがなかったら、コードの数千行下で意図せず同名の変数を宣言した際、
// 未定義の値を参照してアプリケーションが静かにクラッシュするリスクがある。
// TDZは、設計上の「怠慢」をコンパイル(解釈)時点で弾くための防衛線なのだ。
2. メモリ効率とパフォーマンスの観点から
「じゃあ、全部 `const` にすればいいのか?」という問いに対しては、私は「半分正解だが、メモリ管理の観点ではもう少し深い」と答える。
`let` や `const` は、スコープが終了した瞬間にメモリを解放する最適化が走りやすい。対して `var` は関数スコープに依存するため、意図しないクロージャの保持や、グローバルオブジェクトへの汚染が発生しやすく、ガベージコレクション(GC)の効率を著しく下げる要因になる。
特に、大規模なループ処理の中で `var` を使うことは、メモリリークの温床だ。
// 現場でよく見る「メモリの悪夢」
for (var i = 0; i < 10000; i++) {
setTimeout(() => console.log(i), 100);
}
// すべて「10000」が出力される。
// varはブロックスコープを持たないため、全てのクロージャが同じメモリ上のiを参照し続ける。
// これが「非同期の競合」の最も原始的で、最も致命的な例だ。
3. レンダリング負荷と非同期競合への防衛策
上級エンジニアであれば、ReactやVueなどのUIライブラリのレンダリングサイクルと、この変数のスコープがどう噛み合うかを意識しなければならない。
非同期処理の中で宣言された変数が、レンダリングのタイミングとズレて `undefined` を引き起こすバグは、修正に最も時間を食う。特に、`async/await` を多用する現代のコードでは、`let` を適切に使い、TDZを「宣言の強制」として活用することで、競合状態を未然に防ぐのが正解だ。
実践:堅牢なコードのための「宣言の流儀」
// 悪い例: 宣言と初期化が離れている
let user;
// … 50行のロジック …
user = await fetchUser();
// 良い例: 可能な限り宣言と初期化をセットにする
const fetchAndProcess = async () => {
// 必要なタイミングで宣言することで、TDZを正しく「安全装置」として使う
const userData = await fetchUser();
const processedData = transform(userData);
return processedData;
};
結論:コードは「意図」を語るべきだ
`var` を使うことは、現代のJavaScript開発において「私は言語仕様の隙を突くような不確実なコードを書きます」と宣言しているに等しい。
- `const` がデフォルト: 再代入が必要ないものは全て `const` にする。これにより、コードを読む者は「この値は決して変わらない」と確信を持って読める。
- `let` は最小スコープで: ループや条件分岐など、再代入が必要な場所だけに限定する。
- `var` は過去の遺物: レガシーシステムの改修以外で使う理由は、もはや存在しない。
言語仕様の「隙」を理解した上で、あえて「堅牢な道」を選ぶ。それが、複雑なWebアプリケーションを破綻させないための、スペシャリストの矜持だ。
諸君、明日書くコードから、この「初期化の厳格さ」を徹底してみてほしい。バグの温床が驚くほど消滅するはずだ。

コメント