クロージャの基本的な考え方

クロージャ(Closure)とは、関数が作成されたときのスコープ(変数や引数など)を保持したまま、その関数を後から実行できる仕組みです。
通常、関数の中で宣言した変数は関数の実行が終わると利用できなくなります。しかし、クロージャが作成されると、その変数は関数の外からでも内部関数を通じて利用できます。
JavaScriptではクロージャは特別な構文ではなく、関数が外側のスコープを参照した状態になると自動的に作られます。
カウンターを作成するクロージャ
クロージャの代表的な利用例がカウンターです。
関数内の変数を保持し続けることで、呼び出すたびに値を増やすことができます。
カウンターの例
function createCounter() {
let count = 0;
return function () {
count++;
return count;
};
}
const counter1 = createCounter();
console.log(counter1());
console.log(counter1());
const counter2 = createCounter();
console.log(counter2());
console.log(counter1());
// 1
// 2
// 1
// 3
このコードでは、countはcreateCounter()のローカル変数ですが、返された関数が保持しているため消えません。
createCounter()が実行されます。countが作成されます。- 内部関数が返され、
countを保持します。 - 関数を呼び出すたびに同じ
countが更新されます。
また、counter1とcounter2は、それぞれ独立したクロージャを持つため、互いに影響しません。
プライベート変数としてのクロージャ
クロージャを利用すると、外部から直接変更できないプライベート変数のような仕組みを作ることができます。
プライベート変数の例
function createSecret() {
let secret = "秘密のメッセージ";
return function () {
return secret;
};
}
const getSecret = createSecret();
console.log(getSecret());
// 秘密のメッセージ
この例では、secretへ直接アクセスすることはできません。
値を取得できるのは、返された関数を通してだけです。
このように、外部へ公開したくないデータを保持したい場合にもクロージャが利用されます。
イベントハンドラーとクロージャ
クロージャはイベントハンドラーでもよく利用されます。
イベントハンドラーでのクロージャの例
for (let i = 1; i <= 3; i++) {
document
.getElementById("button" + i)
.addEventListener("click", () => {
alert("Button " + i + " clicked");
});
}
このコードでは、各イベントリスナーがそれぞれのiを保持しています。
そのため、クリックしたボタンに対応した番号が表示されます。
なお、現在ではletを使用することで自然に期待どおりの動作になります。
以前はvarを使用していたため、クロージャを利用した工夫が必要になる場面が多くありました。
クロージャが使われる場面
クロージャは実際のWeb開発でも頻繁に利用されています。
- カウンターなど状態を保持する処理
- モジュールパターン
- イベントリスナー
- 非同期処理(PromiseやsetTimeoutなど)
- 設定値やキャッシュの保持
クロージャとメモリ
クロージャは非常に便利ですが、内部で参照している変数は保持され続けます。
そのため、大きなデータを保持したまま不要なクロージャを残してしまうと、メモリ使用量が増える原因になります。
通常の開発では過度に心配する必要はありませんが、不要になったイベントリスナーやオブジェクトを適切に解放することは重要です。
初心者が知っておきたいポイント
- クロージャは特別な構文ではなく、関数が外側の変数を参照すると自動的に作られる。
- 関数が終了しても、必要な変数は保持され続ける。
- 同じ関数から作られたクロージャでも、それぞれ独立した状態を持つ。
- 現在では
letやconstと組み合わせて利用することが多い。 - イベント処理や非同期処理では特に重要な仕組みである。
まとめ
- クロージャとは、関数が作成時のスコープを保持し続ける仕組みである。
- 関数の外からでも、内部関数を通して変数へアクセスできる。
- カウンターやプライベート変数の実装によく利用される。
- イベントハンドラーや非同期処理でも重要な役割を果たす。
- 便利な仕組みだが、不要なデータを保持し続けないよう注意する。