クロージャ: 記憶を持つ関数
関数が定義時の変数を記憶する仕組み。プライベート変数やコールバックで活躍。
クロージャとは?
なぜクロージャが必要か?
クロージャは「外側のスコープの変数を参照し続ける関数」です。JavaScriptでは、関数が定義された場所(レキシカルスコープ)の変数を記憶し続けることができます。これにより、プライベート変数やコールバックで活躍します。
いつ使うか?
クロージャは、以下のような場面で使います: 1. **プライベート変数**: 外部からアクセスできない変数を作りたいとき。 2. **コールバック**: イベントハンドラや非同期処理で状態を保持したいとき。 3. **カリー化**: 関数の部分適用を行いたいとき。 4. **モジュールパターン**: グローバル汚染を防ぎたいとき。
実践テクニック
プライベート変数を作る
クロージャを使うと、外部からアクセスできないプライベート変数を作れます。これにより、グローバル汚染を防ぎ、変数のカプセル化を実現できます。例:カウンター関数でcount変数を隠蔽する。
コールバックで状態を保持する
イベントハンドラや非同期処理で、クロージャを使って状態を保持できます。これにより、コールバックが実行されたときに、正しい状態にアクセスできます。例:ボタンクリックイベントでカウンターをインクリメントする。
カリー化(部分適用)を活用する
カリー化は、複数の引数をとる関数を、1つの引数をとる関数に変換するテクニックです。クロージャを使って実現できます。例:multiply(a)(b)のように、aを固定してbをとる関数を作る。
モジュールパターンを使う
モジュールパターンは、IIFE(即時実行関数式)を使って、プライベート変数とパブリックメソッドを持つオブジェクトを作るパターンです。クロージャを使って実現できます。これにより、グローバル汚染を防ぎ、名前空間の衝突を回避できます。
varとletの違いを理解する
varは関数スコープ、letはブロックスコープです。ループ内でvarを使うと、全ての反復で同じ変数を参照してしまいます(ループの罠)。letを使うと、各反復で新しい変数が作られるため、この問題を回避できます。
クロージャのメモリ管理
クロージャは外側の変数を参照し続けるため、メモリを消費します。不要なクロージャを作ると、メモリリークの原因になります。クロージャを使い終わったら、不要な参照を解放することを推奨します。
クロージャのデバッグ
クロージャは外側の変数を参照し続けるため、デバッグが難しい場合があります。Chrome DevToolsの「Sources」タブでブレークポイントを設定し、クロージャの状態を確認してください。また、`console.log()` を使って、クロージャが参照している変数を出力することも有効です。
function createCounter() { let count = 0; // プライベート変数
return { increment: () => ++count, decrement: () => --count, getCount: () => count };}
const counter = createCounter();counter.increment(); // 1counter.increment(); // 2console.log(counter.getCount()); // 2// count には直接アクセスできない!初心者がハマる罠: ループと var
// ❌ Bad: var は関数スコープfor (var i = 0; i < 3; i++) { setTimeout(() => { console.log(i); // 全て 3 }, 100);}// 出力: 3, 3, 3 (全部同じ!)// ✅ Good: let はブロックスコープfor (let i = 0; i < 3; i++) { setTimeout(() => { console.log(i); // 0, 1, 2 }, 100);}// 出力: 0, 1, 2 (期待通り)実用例
カリー化(部分適用)
// カリー化(部分適用)const multiply = (a) => (b) => a * b;
const double = multiply(2);const triple = multiply(3);
console.log(double(5)); // 10console.log(triple(5)); // 15モジュールパターン
// モジュールパターン(プライベート状態)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); // 100bankAccount.withdraw(30); // 70// bankAccount.balance → undefined (アクセス不可)合格ライン
演習課題
参考文献
この記事は以下の公的ガイドライン/標準に基づいています。
- MDN - Closures - クロージャの公式ドキュメント
- MDN - Using Closures - クロージャの使い方
- MDN - let statement - letとvarの違い
- MDN - Functions - 関数の基礎