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

こんにちは。JavaScriptの深淵へようこそ。

現場でコードを書いていると、「あれ、なんで動かないんだ?」と頭を抱える瞬間がありますよね。特に駆け出しの頃、最も多くのエンジニアが「魔法かよ!」と叫びたくなる瞬間の一つが、「ループの中で動くクロージャ」の挙動です。

大丈夫です。これはあなたが悪いわけではありません。JavaScriptという言語が歩んできた歴史と、少しばかり「おせっかいな仕様」が絡み合っているだけなんです。今日は、このモヤモヤをスッキリ解消してしまいましょう。

—

1. 「なぜか全部同じ結果になる」という悪夢

まず、よくある「罠」を見てみましょう。3つのボタンがあって、それぞれクリックすると「何番目のボタンか」を教えてくれるプログラムを作りたいとします。

// 【悪い例】varを使ったコード
var buttons = [1, 2, 3];

for (var i = 0; i < buttons.length; i++) { // ボタンをクリックした時に実行される「予定」の関数 setTimeout(function() { console.log("これは" + i + "番目のボタンです"); }, 1000); } 直感的には、「これは1番目のボタンです」「これは2番目のボタンです」……と順番に出るはずですよね? でも、実際に実行してみると、全部「これは3番目のボタンです」と表示されてしまうんです。

なぜこんなことが起きるのでしょうか?

—

2. varは「大きな広間」、letは「個室」

この現象を理解する鍵は、「varはどこでも顔を出せるおしゃべり屋」であり、「letは自分の部屋を大切にする職人」だという点にあります。

varという「大きな広間」の罠

`var` で宣言した変数は、プログラム全体から見える「大きな広間」に置かれます。ループを回している間、`i` という変数は一つしか存在せず、ループが終わった瞬間、その広間にある `i` は「3」という値で上書きされて固定されてしまいます。

`setTimeout` の中の関数は、「あとで実行される予定のメモ」です。このメモが実際に読み込まれるのは、ループがすべて終わった後。その時、広間を見渡すと `i` はもう「3」になっているので、「ああ、全部3番目なんだな」と勘違いしてしまうわけです。

letという「個室」の救済

ここで現代のJavaScriptのヒーロー、`let` の登場です。`let` を使うと、「ブロック(`{ }` の中)ごとに個室を作る」という挙動になります。

// 【良い例】letを使ったコード
for (let i = 0; i < 3; i++) { // この { } の中で宣言された i は、その回だけの「特別なi」として保存される setTimeout(function() { console.log("今度はちゃんと" + i + "番目です!"); }, 1000); } `let` を使うと、JavaScriptは「おっと、今のループの `i` はこの個室専用のものだね」と、それぞれの回ごとの `i` をしっかり記憶(クロージャ)してくれます。これが、私たちが期待する「1, 2, 3」という結果を生む正体です。 ---

3. 身近な例えでイメージしてみよう

これ、「レストランの予約票」に例えると分かりやすいかもしれません。

  • varの場合: お店に大きなホワイトボードが1枚だけ置いてあります。店員(JavaScript)は、予約を受け付けるたびにそのボードの数字を書き換えていきます。最後に店員がホワイトボードを見たら、当然「最後に書かれた数字」しか残っていませんよね。
  • letの場合: お客さん一人ひとりに専用の小さなメモ帳を配ります。店員は「あなたはこのメモ帳の数字を見てね」と伝えます。これなら、後で誰が確認しても、それぞれの客が持っているメモには正しい番号が書かれています。

—

4. チーフアーキテクトからのアドバイス

実務の世界では、「基本は `const`、変更が必要なら `let`、`var` は使わない」というのが現在の鉄則です。

昔のコードには `var` がたくさん残っていますが、現代のフロントエンド開発において `var` を使う理由はほとんどありません。`var` は「スコープが広すぎてバグの温床になりやすい」という厄介な側面があるからです。

もし、どうしても昔のコードを直さなきゃいけない場面で、「`let` に変えたら動かなくなった!」ということがあれば、それはそのコードが `var` の「広すぎるスコープ」に依存してしまっている証拠。その時は、焦らずに「この変数はどこで共有されるべきか?」という設計を見直すチャンスだと思ってください。

まとめ:今日から意識すること

1. ループには必ず `let` を使う: これだけで、クロージャの罠の9割は回避できます。
2. `var` は過去の遺産と割り切る: 新しく書くコードには書かないと心に決めましょう。
3. 迷ったら「個室」を作る: `{ }` で囲ってスコープを限定する意識を持つだけで、あなたのコードは格段に堅牢になります。

JavaScriptは柔軟な言語ですが、その分、書き手の意図を明確に伝えることが大切です。「私はここでこの変数を使いたいんだ」という意思を `let` や `const` に込めて、これからも楽しくコーディングしてくださいね。

また何かわからないことがあったら、いつでも聞いてください。壁にぶつかるのは、あなたが成長している何よりの証拠ですから。

コメント

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