やあ。今日もコードと格闘してるかい?
フロントエンドの世界は進化が早いけれど、結局のところ「JavaScriptの言語仕様の深淵」を知っているかどうかが、バグを未然に防ぎ、チームの開発生産性を底上げする。今日は、多くのエンジニアが一度は膝を折る「ループ内でのクロージャの罠」について、現場の視点から深掘りしてみよう。
なぜ「var」を使うと全てが台無しになるのか
まずは、この悪名高いコードを見てほしい。おそらく一度は見たことがあるはずだ。
// 【アンチパターン】varを使って意図しない挙動を生む例
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log('現在の値は:', i);
}, 100);
}
// 期待値: 0, 1, 2
// 実際の挙動: 3, 3, 3
なぜこうなるのか。理由はシンプルで、`var`にはブロックスコープがないからだ。
`var`で宣言された変数`i`は、ループが終わった瞬間にスコープを抜け出せず、その関数スコープ全体で同じメモリ領域を共有してしまう。`setTimeout`のコールバックが実行される頃には、ループはすでに完了しており、`i`は最後に入った値である`3`に固定されている。これが「巻き上げ(Hoisting)」と「関数スコープ」が生んだ悲劇だ。
昔の現場では、これを回避するために即時実行関数(IIFE)を使って値をスコープに閉じ込めていた。
// 【昔の知恵】IIFEによるスコープの切り離し
for (var i = 0; i < 3; i++) {
(function(lockedI) {
setTimeout(function() {
console.log('IIFEで保護された値:', lockedI);
}, 100);
})(i);
}
確かに動く。でも、現代のフロントエンド開発でこれを書く理由はもう一つもない。
「let」がもたらした革命:ブロックスコープの正体
ES6で導入された`let`は、ただの「書き換え可能な変数」じゃない。最大の恩恵は「ループの各イテレーションごとに新しいスコープが生成される」という仕様にある。
ブラウザのエンジン(V8など)は、`let`を使ったループを非常に賢く処理している。ループが回るたびに、裏側では新しい「環境レコード(Environment Record)」が作られ、そこにその時点の`i`が個別に保存されるんだ。
// 【モダンな解法】letを使うだけで全て解決する
for (let i = 0; i < 3; i++) {
// ここで生成されるスコープは、このループの回数分だけ独立して存在する
setTimeout(function() {
console.log('letが守る値:', i);
}, 100);
}
// 期待通り: 0, 1, 2 と出力される
これだけでいい。`let`を使うだけで、クロージャは生成された瞬間の「その時の`i`」を記憶し続ける。コードがクリーンで、かつ予期せぬ副作用も生まれない。これが現代のJavaScriptの正攻法だ。
実務で「罠」を避けるためのベストプラクティス
現場でレビューをする際、私は以下のルールを徹底している。
1. `var`は禁止する: ESLintの設定で`no-var`を強制的にエラーにする。もはや`var`を使う理由は、レガシーシステムの改修以外には存在しない。
2. `const`を最優先し、`let`は最小限に: 再代入の必要がないなら、何があっても`const`を使う。スコープを限定し、データの不変性を守ることが、複雑な非同期処理を扱う際のバグを激減させる秘訣だ。
3. ループの意図を明確にする: もしループ内で複雑な非同期処理(`Promise.all`など)を行うなら、`Array.prototype.map`や`for…of`を使うことを検討しよう。
// 【実務での推奨】forループを避け、関数型のアプローチを取る
const items = [0, 1, 2];
// 配列を変換して処理する方が、スコープの管理がより明示的になる
items.forEach((i) => {
setTimeout(() => console.log(‘forEachなら安全:’, i), 100);
});
最後に
「なぜ動かないのか」ではなく「ブラウザがメモリの中でどう変数を扱っているか」を想像できるようになると、君のコーディングは劇的に変わる。
JavaScriptは柔軟で、時に残酷なほど自由だ。だからこそ、我々エンジニアが「意図したスコープ」をコードで表現してやらなきゃいけない。`var`を捨てて、ブロックスコープを信じる。それだけで、君が書くコードの信頼性はグッと高まるはずだ。
もしまた何か深い部分で迷ったら、いつでも聞いてくれ。コードの裏側には、常に面白い発見が眠っているからな。

コメント