【実務・中級編】 ループ内でのブロックスコープとクロージャの罠 – JavaScript実践ガイド

やあ。今日もコードと格闘してるかい?

フロントエンドの世界は進化が早いけれど、結局のところ「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`を捨てて、ブロックスコープを信じる。それだけで、君が書くコードの信頼性はグッと高まるはずだ。

もしまた何か深い部分で迷ったら、いつでも聞いてくれ。コードの裏側には、常に面白い発見が眠っているからな。

コメント

タイトルとURLをコピーしました