JavaScriptのクロージャとは?仕組みと使い方を基礎から徹底解説

関数とスコープの関係性を視覚的に表現したJavaScriptクロージャのイメージ
関数が外部の変数を保持する仕組みをイメージした、JavaScriptのクロージャのビジュアル

JavaScriptを学習していると、「クロージャ(closure)」という言葉に必ず出会います。
一見すると難しそうに見えますが、仕組みを理解すると関数の理解が一段深まり、実務でも非常に役立つ重要な概念です。

この記事では、クロージャの基本から実践的な使い方まで、基礎をしっかり固められるように解説します。

クロージャとは何か

クロージャとは、関数が外側のスコープの変数を記憶したまま使える仕組みのことです。

JavaScriptでは、関数は作成されたときのスコープ(変数の環境)を保持します。
そのため、関数の外で定義された変数でも、関数内から参照できます。

クロージャの基本例

まずはシンプルなコードで確認します。

// closure-basic.js
function outer() {
  const message = "こんにちは";

  function inner() {
    console.log(message);
  }

  return inner;
}

const fn = outer();
fn(); // こんにちは

ポイント

  • innerouterの中で定義されている
  • outerの実行後でも、messageを保持している
  • これがクロージャの本質

なぜクロージャが成立するのか

JavaScriptの関数は、定義されたときのスコープを保持するという性質を持っています。

そのため、関数が返されたあとでも、元のスコープにアクセスできます。

この仕組みは、以下の記事で解説している関数の基本理解と深く関係しています。
JavaScriptの関数とは?基本から使い方まで解説

クロージャのイメージ

クロージャは以下のように考えると理解しやすいです。

  • 関数は「変数を覚えた箱」
  • 外側の変数を持ったまま動く

つまり、

「関数 + そのときの環境」=クロージャ

という構造になっています。

クロージャの実用例①:カウンター

クロージャは、状態を保持する処理でよく使われます。

// closure-counter.js
function createCounter() {
  let count = 0;

  return function () {
    count++;
    console.log(count);
  };
}

const counter = createCounter();

counter(); // 1
counter(); // 2
counter(); // 3

ポイント

  • countは外から直接触れない
  • 関数だけがcountにアクセスできる
  • 状態を安全に管理できる

クロージャの実用例②:設定を保持する関数

// closure-config.js
function createGreeting(greeting) {
  return function (name) {
    console.log(greeting + "、" + name + "さん");
  };
}

const hello = createGreeting("こんにちは");
const goodMorning = createGreeting("おはよう");

hello("太郎");       // こんにちは、太郎さん
goodMorning("花子"); // おはよう、花子さん

ポイント

  • 引数greetingを保持している
  • 同じ関数でも違う設定で使い回せる

クロージャのメリット

状態を安全に管理できる

外部から直接アクセスできない変数を作れるため、意図しない変更を防げます。

関数の再利用性が高まる

設定や条件を保持した関数を作ることで、柔軟な処理が可能になります。

カプセル化ができる

オブジェクト指向でいう「カプセル化」に近い役割を果たします。

クロージャの注意点

メモリに残り続ける

クロージャは外側の変数を保持するため、不要になったら参照を切ることが重要です。

// closure-memory.js
function createData() {
  const largeData = new Array(1000000).fill("data");

  return function () {
    console.log(largeData.length);
  };
}

let fn = createData();
fn();

// 不要になったら解放
fn = null;

よくある疑問:なぜループで問題が起きるのか

クロージャはループと組み合わせると、意図しない動作になることがあります。

// closure-loop-problem.js
for (var i = 0; i < 3; i++) {
  setTimeout(function () {
    console.log(i);
  }, 100);
}
// 3, 3, 3 と出力される

解決方法(letを使う)

// closure-loop-fix.js
for (let i = 0; i < 3; i++) {
  setTimeout(function () {
    console.log(i);
  }, 100);
}
// 0, 1, 2 と出力される

この違いは、スコープの仕組みによるものです。

JavaScriptのスコープとは?var・let・constの違いを解説

クロージャと非同期処理の関係

クロージャは非同期処理でも重要な役割を持ちます。

// closure-async.js
function fetchData(url) {
  return function () {
    console.log("取得中:", url);
  };
}

const request = fetchData("https://example.com");
request();

Promiseやthenなどの理解にもつながります。

JavaScriptの非同期処理とは?Promiseとasync/awaitの基礎
JavaScriptのthenの使い方

クロージャを使う場面

クロージャは以下のような場面で活躍します。

  • カウンターや状態管理
  • 設定付き関数の生成
  • イベント処理
  • 非同期処理の制御

特にイベント処理については、以下の記事で詳しく解説しています。
JavaScriptのイベントとは?クリック・入力処理の基本を解説

まとめ

クロージャは、JavaScriptにおける重要な概念のひとつです。

  • 関数はスコープを記憶する
  • 外側の変数を保持したまま動作する
  • 状態管理や再利用性の高い処理に役立つ

最初は難しく感じますが、基本的なコードを繰り返し確認することで理解が深まります。

関連記事