変数のスコープ(関数の中と外)

📝 問題の再掲

復習Q6: 関数内で let count = 0; と宣言してカウンターを作ったが、呼ぶたびに 1 しか返らない。なぜ?

→ 関数が呼ばれるたびに count が 0 に初期化されるため。

⚠️ よくある間違い

  • 関数内の変数が外からアクセスできると思う
  • 関数を呼ぶたびにローカル変数がリセットされることに気づかない
  • グローバル変数とローカル変数の区別がつかない

📖 段階的な説明

ステップ1:グローバルスコープとローカルスコープ

  • グローバルスコープ: 関数の外で宣言した変数。どこからでもアクセスできる
  • ローカルスコープ: 関数の中で宣言した変数。その関数の中でしか使えない

ステップ2:関数の中の変数は外から見えない

function greet() {
  let message = "こんにちは";  // ローカル変数
  console.log(message);        // ✅ OK
}
greet();
console.log(message);  // ❌ エラー! message は関数の外から見えない

ステップ3:関数が呼ばれるたびにリセットされる

// ❌ うまくいかない例
function countUp() {
  let count = 0;   // 呼ぶたびに 0 に戻る!
  count++;
  return count;    // 毎回 1 が返る
}
console.log(countUp()); // 1
console.log(countUp()); // 1(2にならない!)

ステップ4:解決策 — 関数の外に変数を置く

// ✅ 正しい例
let count = 0;  // グローバルに置く

function countUp() {
  count++;      // 外の変数を更新
  return count;
}
console.log(countUp()); // 1
console.log(countUp()); // 2 ✅

ただし、グローバル変数を増やしすぎると、どこからでも書き換えられてバグの原因になります。次の「クロージャ」を使うと、外から直接触れない安全な形で変数を保持できます。

💡 たとえ話

関数 = 部屋、変数 = 部屋の中のメモ

部屋(関数)に入るたびに新しいメモ用紙が配られます。部屋を出たらメモは捨てられます。次に入ったときはまた新しいメモからスタートです。

部屋の外の廊下(グローバル)に貼ったメモは、何度部屋に出入りしても残り続けます。

🔒 クロージャとは

スコープの仕組みを利用すると、クロージャ(closure)という便利な仕組みが作れます。クロージャとは、関数が「外側の変数」を覚えたまま持ち歩く仕組みのことです。

通常、関数の中のローカル変数は関数を抜けると消えます。しかし、関数の中で別の関数(内側の関数)を作って返すと、内側の関数は外側の変数を覚え続けます。これがクロージャです。

定番の例:カウンター

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 を「覚えたまま」なので、counter() を呼ぶたびに count が引き継がれて増えていきます。

ふつうならローカル変数は関数を抜けると消えますが、クロージャを使うと変数を安全に保持しながら、外から直接触れないようにできます。カウンターや設定値の管理などでよく使われるテクニックです。

✅ 確認問題

Q: 以下のコードで console.log(x) は何を表示する?

let x = 10;
function change() {
  let x = 20;
}
change();
console.log(x);
答えを見る

10 が表示されます。関数内の let x = 20 はローカル変数なので、グローバルの x には影響しません。

🔗 関連リソース

← ドリルのQ6に戻る