クロージャ: 記憶を持つ関数

関数が定義時の変数を記憶する仕組み。プライベート変数やコールバックで活躍。

クロージャ
外側のスコープの変数を参照し続ける関数。
レキシカルスコープ
関数が定義された場所で変数を解決する仕組み。
プライベート変数
外部からアクセスできない、関数内に隠された変数。

クロージャとは?

思い出アルバム

クロージャは「思い出を持つ関数」。生まれた場所(定義時のスコープ)の変数を覚えている。どこに移動しても、その思い出にアクセスできる。

なぜクロージャが必要か?

クロージャは「外側のスコープの変数を参照し続ける関数」です。JavaScriptでは、関数が定義された場所(レキシカルスコープ)の変数を記憶し続けることができます。これにより、プライベート変数やコールバックで活躍します。

いつ使うか?

クロージャは、以下のような場面で使います: 1. **プライベート変数**: 外部からアクセスできない変数を作りたいとき。 2. **コールバック**: イベントハンドラや非同期処理で状態を保持したいとき。 3. **カリー化**: 関数の部分適用を行いたいとき。 4. **モジュールパターン**: グローバル汚染を防ぎたいとき。

実践テクニック

プライベート変数を作る

クロージャを使うと、外部からアクセスできないプライベート変数を作れます。これにより、グローバル汚染を防ぎ、変数のカプセル化を実現できます。例:カウンター関数でcount変数を隠蔽する。

コールバックで状態を保持する

イベントハンドラや非同期処理で、クロージャを使って状態を保持できます。これにより、コールバックが実行されたときに、正しい状態にアクセスできます。例:ボタンクリックイベントでカウンターをインクリメントする。

カリー化(部分適用)を活用する

カリー化は、複数の引数をとる関数を、1つの引数をとる関数に変換するテクニックです。クロージャを使って実現できます。例:multiply(a)(b)のように、aを固定してbをとる関数を作る。

モジュールパターンを使う

モジュールパターンは、IIFE(即時実行関数式)を使って、プライベート変数とパブリックメソッドを持つオブジェクトを作るパターンです。クロージャを使って実現できます。これにより、グローバル汚染を防ぎ、名前空間の衝突を回避できます。

varとletの違いを理解する

varは関数スコープ、letはブロックスコープです。ループ内でvarを使うと、全ての反復で同じ変数を参照してしまいます(ループの罠)。letを使うと、各反復で新しい変数が作られるため、この問題を回避できます。

クロージャのメモリ管理

クロージャは外側の変数を参照し続けるため、メモリを消費します。不要なクロージャを作ると、メモリリークの原因になります。クロージャを使い終わったら、不要な参照を解放することを推奨します。

クロージャのデバッグ

クロージャは外側の変数を参照し続けるため、デバッグが難しい場合があります。Chrome DevToolsの「Sources」タブでブレークポイントを設定し、クロージャの状態を確認してください。また、`console.log()` を使って、クロージャが参照している変数を出力することも有効です。

Private State with Closure
function createCounter() {
let count = 0; // プライベート変数
return {
increment: () => ++count,
decrement: () => --count,
getCount: () => count
};
}
const counter = createCounter();
counter.increment(); // 1
counter.increment(); // 2
console.log(counter.getCount()); // 2
// count には直接アクセスできない!
ポイント: count は外部からアクセスできない。これがJavaScriptでプライベート変数を作る古典的な方法。

初心者がハマる罠: ループと var

Bad
// ❌ Bad: var は関数スコープ
for (var i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i); // 全て 3
}, 100);
}
// 出力: 3, 3, 3 (全部同じ!)
Good
// ✅ Good: let はブロックスコープ
for (let i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i); // 0, 1, 2
}, 100);
}
// 出力: 0, 1, 2 (期待通り)
なぜ?

var は関数スコープなので、全ての setTimeout が同じ i を参照する。let はブロックスコープなので、各ループで別の i が作られる。

実用例

カリー化(部分適用)

Currying
// カリー化(部分適用)
const multiply = (a) => (b) => a * b;
const double = multiply(2);
const triple = multiply(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15

モジュールパターン

Module Pattern
// モジュールパターン(プライベート状態)
const bankAccount = (() => {
let balance = 0; // プライベート
return {
deposit: (amount) => {
balance += amount;
return balance;
},
withdraw: (amount) => {
if (amount > balance) {
throw new Error('Insufficient funds');
}
balance -= amount;
return balance;
},
getBalance: () => balance
};
})();
bankAccount.deposit(100); // 100
bankAccount.withdraw(30); // 70
// bankAccount.balance → undefined (アクセス不可)

合格ライン

クロージャが何かを説明できる
ループ内で let を使う理由を説明できる
プライベート変数をクロージャで作れる

演習課題

課題1: クロージャ
クロージャでプライベート変数を作成してください。
課題2: モジュールパターン
モジュールパターンを実装してください。

参考文献

この記事は以下の公的ガイドライン/標準に基づいています。