スコープ + クロージャ — *変数が生きる領域*
スコープ + クロージャ — *変数が生きる領域*
🎯 このレッスンを読み終えたら
このレッスンを読み終えると、以下の3つを自信を持ってできるようになります。
- ▸✅ Lexical Scope + クロージャの定義とコード例
- ▸✅ クロージャでプライベート変数(カウンター・キャッシュ)を作る
- ▸✅
varの関数スコープの落とし穴とletによる解決方法
学習目標をチェックリストとして手元に置き、すべてに答えられるようになったらレッスンを閉じてください。
スコープ — *どこから変数にアクセスできるか*
核心の一文
スコープ (Scope) = 変数が アクセス可能な範囲。JS のスコープは3種類: グローバル・関数・ブロック。
3つのスコープ
console.log(varVar); // ✅ var は if の外からもアクセス可能
}
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
// グローバル
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 はそれぞれ別の貯金箱を持っている → 互いに影響しない
javascriptfunction 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 — 直接アクセス不可
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` は ブロックスコープ(予測可能) - ▸クロージャ = 関数が 自分の環境を記憶 する
- ▸プライベートデータ・カプセル化 に活用
⚡ 自分で試してみよう — 貯金箱クロージャ
🤖 AIへのリクエスト例
このレッスンの概念を知ると、AIに 具体的に 指示できるようになります。漠然とした「直して」ではなく、語彙を持ったリクエスト — それがトークン節約の出発点です。
- ▸「このコードの
varをすべてconst/letに変えて」 - ▸「再代入の可能性を分析して
const優先で整理して」 - ▸「このコードで hoisting の問題が起きる可能性を診断して」
なぜこれがトークンを減らすのか
概念を知らないと、AI の回答を受け取っても 「それって何ですか?」 と再度聞かなければなりません。その「再質問」がトークンを消費します。概念を一度身につければ、会話が一度で終わります。