C
JavaScript/関数/Lesson 09

スコープ + クロージャ — *変数が生きる領域*

1時間·theory
このチャプター
3/4

スコープ + クロージャ — *変数が生きる領域*

🎯 このレッスンを読み終えたら

このレッスンを読み終えると、以下の3つを自信を持ってできるようになります。

  • ✅ Lexical Scope + クロージャの定義とコード例
  • ✅ クロージャでプライベート変数(カウンター・キャッシュ)を作る
  • var の関数スコープの落とし穴と let による解決方法

学習目標をチェックリストとして手元に置き、すべてに答えられるようになったらレッスンを閉じてください。

スコープ — *どこから変数にアクセスできるか*

核心の一文

スコープ (Scope) = 変数が アクセス可能な範囲。JS のスコープは3種類: グローバル・関数・ブロック

3つのスコープ

javascript
// グローバルスコープ — どこからでもアクセス可能
const globalVar = "グローバル";

function outer() {
    // 関数スコープ — outer の中だけ
    const funcVar = "関数";

    if (true) {
        // ブロックスコープ — if の中だけ
        let blockVar = "ブロック";
        const alsoBlock = "ここも";

        // var は関数スコープ(ブロックを無視)
        var varVar = "var は関数スコープ";
    }

    console.log(funcVar);     // ✅
    console.log(blockVar);    // ❌ ReferenceError

console.log(varVar); // ✅ var は if の外からもアクセス可能

}

code

- **`let`·`const`** — ブロックスコープ(`{}` の中)
- **`var`** — 関数スコープ(`var` のもう一つの落とし穴)
- **宣言キーワードなし** — グローバル(絶対 NG、バグの原因)

## スコープチェーン — *外側を上へたどる*

javascript

// 🌍 グローバルスコープ

const a = "グローバル";

function outer() {
// 📦 outer 関数スコープ(グローバルの内側)
const b = "outer";

function inner() {
// 🎯 inner 関数スコープ(outer の内側)
const c = "inner";

console.log(c); // "inner" ← ① 自スコープに c あり → そのまま使用
console.log(b); // "outer" ← ② 自スコープに b なし → outer まで上がって発見
console.log(a); // "グローバル" ← ③ outer にも a なし → グローバルまで上がって発見
}
inner(); // 🚀 inner を実行 — 上の3行の console.log が出力される
}

outer(); // ▶️ 実行開始! — この呼び出しがなければ outer も inner も永遠に実行されない

// 📤 コンソール出力(上から下へ):

// inner

// outer

// グローバル

code

JS が変数を探すとき:
1. 現在のスコープ
2. 外側の関数スコープ(あれば)
3. グローバルスコープ

この*連鎖的な探索*がスコープチェーン。

## クロージャ (Closure) — *関数が自分の環境を記憶する*
**一言定義**: 関数が *自分が生まれた場所の変数* を持ち続けること。例え — 🐷 貯金箱(`count`)を持ち歩く 🤚 手(返された関数)。

javascript

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

// 1️⃣ 貯金箱を作る工場 (makeCounter)

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

function makeCounter() {

// 🐷 新しい貯金箱を作る(中にコイン0枚)

let count = 0;

// 🤚 コインを入れる「手」を作って返す
return function () {
count++; // 貯金箱にコインを1枚追加
return count; // 現在のコイン枚数を返す
};
// ↑ この関数は上の count 変数を「記憶」したまま外に出ていく!
// → makeCounter が終わっても count は消えない(これがクロージャ!)
}

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 2️⃣ 貯金箱を受け取る
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
const counter = makeCounter();
// ↑ ↑
// │ └─ 工場稼働 → 🐷 + 🤚 セットを作って出荷
// └─ 受け取ったセットを counter 変数に保存
// → counter はこれで「貯金箱にコインを入れる手」の役割!

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 3️⃣ 手でコインを入れる(counter を呼び出す)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
console.log(counter()); // 1 ← 🤚 コイン投入 → 🐷 中: 1枚
console.log(counter()); // 2 ← また投入 → 🐷 中: 2枚(同じ貯金箱!)
console.log(counter()); // 3 ← またまた投入 → 🐷 中: 3枚(どんどん貯まる!)

// 💡 ポイント:
// - 毎回新しい貯金箱を作るわけではない!
// - counter() の呼び出しは同じ貯金箱にコインを積み続ける行為
// - だから 1, 2, 3 と増えていく

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 4️⃣ 新しい貯金箱が必要な場合は?
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
const counter2 = makeCounter(); // 🐷 新しい貯金箱 + 🤚 新しい手(完全に独立!)

console.log(counter2()); // 1 ← 新しい貯金箱なので 0 からスタート!
console.log(counter2()); // 2
console.log(counter()); // 4 ← 最初の貯金箱は 3 から続く!

// 💡 counter と counter2 はそれぞれ別の貯金箱を持っている → 互いに影響しない

code

**🎬 ステップごとの整理**

- **[ステップ1]** `makeCounter()` を呼び出す — 🏭 工場が 🐷 貯金箱(`count=0`)を作り、その貯金箱にアクセスできる 🤚 手を出荷
- **[ステップ2]** `const counter = ...` — 🤚🐷 セットを counter 変数に保存。工場(`makeCounter`)は閉まるが、counter が手を掴んでいるので貯金箱も生き続ける!
- **[ステップ3]** `counter()` を呼び出すたびに — 🤚 が 🐷 にコインを1枚追加し、現在の枚数を返す

**🎯 クロージャ = 「内部変数を外から安全に操作するハンドル」**

メリット: ✅ 変数が外部に露出しない(セキュリティ) ✅ 呼び出し間で値が保持される(状態記憶) ✅ 各呼び出しが独立して自分の環境を持つ

## クロージャの活用 — *プライベート変数*

javascript

function makeBank(initialBalance) {

let balance = initialBalance; // 外部から直接アクセス不可

return {
deposit(amount) { balance += amount; },
withdraw(amount) {
if (amount > balance) throw new Error("残高不足");
balance -= amount;
},
getBalance() { return balance; }
};
}

const account = makeBank(1000);

account.deposit(500);

account.getBalance(); // 1500

account.balance; // undefined — 直接アクセス不可

code

`balance` は *関数の内部* にあるため、外部から *見ることができません*。定められたメソッド(`deposit`·`withdraw`)でのみ操作可能。これが JS における*カプセル化*の方法です。

## よくある落とし穴 — *ループ + `var`*

javascript

// ❌ var の落とし穴

for (var i = 0; i < 3; i++) {

setTimeout(() => console.log(i), 1000);

}

// 出力: 3, 3, 3 (すべて同じ i)

// ✅ let — ブロックスコープ
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1000);
}
// 出力: 0, 1, 2
``

var関数スコープ なのでループのすべての反復が 同じ i を共有。letブロックスコープ なので反復ごとに 新しい i が生まれる。

まとめ

  • スコープ = 変数の アクセス範囲
  • let·const`ブロックスコープ(予測可能)
  • クロージャ = 関数が 自分の環境を記憶 する
  • プライベートデータ・カプセル化 に活用

⚡ 自分で試してみよう — 貯金箱クロージャ

🐷 同じ貯金箱 vs. 新しい貯金箱。クロージャが「自分の環境を記憶する」とはどういう意味か、実際に確かめてみましょう。
✏️ JS 코드
📟 コンソール出力
▶ 実行ボタンを押してください
⚠️ ブラウザのサンドボックスで実行 — console.log()のみ対応、alert/fetchは不可

🤖 AIへのリクエスト例

このレッスンの概念を知ると、AIに 具体的に 指示できるようになります。漠然とした「直して」ではなく、語彙を持ったリクエスト — それがトークン節約の出発点です。

  • 「このコードの var をすべて const/let に変えて」
  • 「再代入の可能性を分析して const 優先で整理して」
  • 「このコードで hoisting の問題が起きる可能性を診断して」

なぜこれがトークンを減らすのか

概念を知らないと、AI の回答を受け取っても 「それって何ですか?」 と再度聞かなければなりません。その「再質問」がトークンを消費します。概念を一度身につければ、会話が一度で終わります

スコープとクロージャ - JavaScript