JavaScriptのクロージャとは?スコープの仕組みと使い方を解説

PR JavaScript

JavaScriptのクロージャとは?スコープの仕組みと使い方を解説

💡 この記事でわかること

  • スコープ(変数が見える範囲)とは何か
  • 関数がどうやってスコープを作るか
  • クロージャとは何か、その仕組み
  • クロージャの具体的な使い方(カウンター関数など)
  • 初心者がつまずきやすいポイント

1. スコープとは?

スコープとは、
👉 「その変数がコードのどの範囲から見えるか(参照できるか)」を決めるルールのことです。

スコープには大きく分けて2種類あります。

  • グローバルスコープ:どこからでも参照できる範囲(関数の外で宣言した変数)
  • ローカルスコープ:特定の関数やブロックの中でしか参照できない範囲
const globalMessage = "どこからでも見える";

function showMessage() {
  const localMessage = "この関数の中でしか見えない";
  console.log(globalMessage); // OK
  console.log(localMessage);  // OK
}

showMessage();
console.log(globalMessage); // OK
console.log(localMessage);  // エラー:localMessageは参照できない

💡 関数の中で宣言した変数は、その関数の外からは見えません。これを「関数がローカルスコープを作る」と表現します

👉 let・const・varの違いについての記事はこちら


2. 関数はネストするとスコープも入れ子になる

関数の中にさらに関数を書く(ネストする)と、内側の関数からは外側の関数の変数を参照できます。

function outer() {
  const outerValue = "外側の変数";

  function inner() {
    console.log(outerValue); // 外側の変数を参照できる
  }

  inner();
}

outer(); // "外側の変数"

👉 内側の関数(inner)は、自分のスコープに変数がなければ、1つ外側のスコープを探しにいきます。これを「スコープチェーン」と呼びます。クロージャは、この仕組みの上に成り立っています。


3. クロージャとは?

クロージャとは、
👉 関数が「自分が作られたときのスコープ」を覚えたまま持ち運べる仕組みのことです。

言葉だけだと分かりにくいので、実際のコードで見てみましょう。

function createCounter() {
  let count = 0;

  return function () {
    count++;
    return count;
  };
}

const counter = createCounter();

console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3

💡 createCounter()の実行が終わると、本来ならcount変数は消えるはずです。しかし、内側で作られた関数がcountを参照し続けているため、countは消えずに残り続けます

この「外側の関数が終わったあとも、内側の関数が外側の変数を覚えていて使い続けられる」状態こそがクロージャです。counterを呼ぶたびに、記憶しているcountが1ずつ増えていくのが確認できます。


4. クロージャの使いどころ:変数を外から直接触れなくする

クロージャの便利な点の1つは、
👉 変数を関数の外から直接書き換えられないように守れることです。

const counter = createCounter();

counter.count = 100; // これはcountに何の影響も与えない
console.log(counter()); // 4(前の続きから正しくカウントされる)

👉 count変数はcreateCounter関数のスコープに閉じ込められていて、外部から直接アクセスする手段がありません。用意した関数を通してしか操作できないため、意図しない値の書き換えを防げます。このような使い方を「カプセル化」と呼びます。

👉 JavaScriptのオブジェクトについての記事はこちら


5. 初心者がつまずきやすいポイント:ループとクロージャ

クロージャの理解でよくつまずくのが、ループの中で関数を作るケースです。

// varを使った場合
const funcs = [];

for (var i = 0; i < 3; i++) {
  funcs.push(function () {
    console.log(i);
  });
}

funcs[0](); // 3(0を期待していたのに...)
funcs[1](); // 3
funcs[2](); // 3

💡 varはループ全体で1つの変数iを共有するため、すべての関数が同じiを参照してしまいます。ループが終わったときのiの値(3)を、あとから全員が見ることになるのです

letを使うと、ループの1回ごとに新しいスコープのiが作られるため、期待通りの結果になります。

// letを使った場合
const funcs = [];

for (let i = 0; i < 3; i++) {
  funcs.push(function () {
    console.log(i);
  });
}

funcs[0](); // 0
funcs[1](); // 1
funcs[2](); // 2

👉 「ループの中で作った関数が、みんな同じ値を参照してしまう」というバグに出会ったら、varをletに変えることで解決できる場合が多いので覚えておきましょう


まとめ

  • スコープとは、変数が参照できる範囲のこと
  • 関数の中で関数を作ると、内側の関数は外側の変数を参照できる
  • クロージャとは、関数が作られたときのスコープを覚えたまま持ち運べる仕組み
  • 変数を外から直接触れなくする「カプセル化」に活用できる
  • ループの中でvarを使うと、クロージャが全員同じ値を参照してしまうので注意

クロージャは最初は少し難しく感じますが、「関数は自分が生まれた場所の変数を覚えている」というイメージさえつかめれば、コードを読むときの理解がぐっと深まります。実際に手を動かしてコードを書き換えながら試してみましょう。

-JavaScript
-, ,