なぜ我々は `var` を過去の遺物として葬り去るべきなのか:forループにおけるスコープの深淵
フロントエンドの戦場において、「動けばいい」というコードは、数ヶ月後の自分自身やチームにとっての地雷原に他ならない。特に、JavaScriptの歴史的経緯を背負った `var` という存在は、モダンなアプリケーションの堅牢性を損なう最大の要因の一つだ。
今回は、あえて初心者が踏み込みがちな `for` ループにおける `var` と `let` の非対称な挙動を解剖し、それがメモリや非同期処理、ひいてはブラウザの実行コンテキストにどのような影響を及ぼすのか、アーキテクトの視点から紐解いていこう。
1. `var` が引き起こす「共有」という名の呪い
まず、以下のコードを見てほしい。おそらく、一度は誰もが通過する「罠」だ。
for (var i = 0; i < 3; i++) {
// 非同期処理をシミュレート(実際にはAPIリクエストやタイマーなど)
setTimeout(() => {
console.log(`現在の値: ${i}`);
}, 100);
}
// 実行結果:
// 現在の値: 3
// 現在の値: 3
// 現在の値: 3
なぜ `0, 1, 2` ではなく `3` が3回出力されるのか? 理由は単純だが残酷だ。`var` は関数スコープ(またはグローバルスコープ)しか持たないからだ。
`for` ループ内で宣言された `var i` は、ループの外側から見れば単なる一つの変数の使い回しに過ぎない。JavaScriptエンジンはループを回すたびに `i` を更新し続けるが、`setTimeout` のコールバック関数が実行される頃には、ループは既に終了し、`i` は終了条件である `3` に到達している。これら全ての非同期タスクは、単一のメモリ上の `i` を共有しているのだ。
2. `let` がもたらす「ループごとのバインディング」
対して、ES6で導入された `let` は、言語仕様レベルで我々の悩みを解消した。
for (let i = 0; i < 3; i++) {
setTimeout(() => {
console.log(`現在の値: ${i}`);
}, 100);
}
// 実行結果:
// 現在の値: 0
// 現在の値: 1
// 現在の値: 2
この違いは、JavaScriptエンジンの内部挙動にある。`let` を使用した場合、エンジンはループの反復ごとに「新しい環境レコード(Environment Record)」を生成する。つまり、各反復における `i` は、独立したメモリ領域として確保されるのだ。
クロージャが作成される際、その時の `i` の値が静的にキャプチャされるため、非同期処理が実行されるタイミングで、それぞれのコールバックは「自分専用の `i`」を参照できる。これは単なるシンタックスシュガーではなく、言語仕様として「ループの反復ごとに変数を再束縛する」というルールが明文化されているからこそ実現できる挙動だ。
3. アーキテクチャ視点:パフォーマンスとメモリ効率
「ループごとにスコープを作るなら、`let` の方がメモリ効率が悪いのでは?」という疑問を持つかもしれない。しかし、現代のV8エンジン等の最適化は極めて優秀だ。
- デッドコード削除と最適化: エンジンは、その変数が実際にクロージャでキャプチャされているかを静的解析で判断する。不要であれば、ループ内のスコープ生成コストは極限まで抑えられる。
- 非同期処理の競合回避: `var` でクロージャを強引に作るために、即時関数(IIFE)を多用していた時代があっただろう。あれは実行コンテキストを無理やり生成するコストがかかり、コードの可読性を著しく下げていた。`let` を使うことは、最適化の余地をエンジン側に提供しつつ、人間が読む際の「スコープの境界」を明確にする、最も合理的な選択なのだ。
4. 堅牢なアプリケーションのために
上級エンジニアとして意識すべきは、「バグを埋め込まないコード」ではなく、「バグが混入する隙間を構造的に排除するコード」である。
- 宣言の強制: `let` と `const` を使い分け、再代入の可能性をコンパイル(トランスパイル)時点で制限せよ。
- スコープの最小化: ループ変数はループのスコープ内に閉じ込める。これがリークを防ぎ、メモリガベージコレクション(GC)が効率的に働ける状態を作る。
- 設計への反映: もしループの中で複雑な非同期処理を並列実行し、その結果を管理する必要があるなら、`for` ループを回すよりも `Array.prototype.map` を活用し、`Promise.all` でパイプラインを組むスタイルへ移行することを推奨する。これにより、変数の変異(Mutation)そのものを排除できる。
結び:技術への敬意
`var` が悪なのではない。かつてのJavaScriptが、ブラウザの非力な環境の中で必死に動的な制御を詰め込もうとした足跡が `var` なのだ。しかし、今の我々は、その泥臭い歴史を理解した上で、より洗練された道具(`let`, `const`, `async/await` など)を選ぶことができる。
「なぜそう動くのか」をエンジンの内部構造まで深掘りする姿勢こそが、複雑なWebアプリケーションを制御下に置く唯一の道だ。今日から、コードベースの `var` を排除し、スコープの秩序を取り戻してほしい。それが、世界最高峰のフロントエンドを構築する第一歩となるはずだ。

コメント