変数のスコープ(関数の中と外)
📝 問題の再掲
復習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 には影響しません。