🐱

【JavaScript】クロージャについて解説

2023/06/24に公開

JavaScript初心者の壁・クロージャについて解説します。あくまで現時点での筆者の理解の範囲での解説となりますが参考になれば嬉しいです。

前提知識:レキシカルスコープとは

クロージャの解説に入る前にレキシカルスコープの解説をします。レキシカルスコープはクロージャを理解する上で非常に重要な概念となるからです。レキシカルスコープについて理解している人はこのセクションを飛ばしてください。

レキシカルスコープとは、記述する場所によって参照出来る変数が異なるスコープです。具体的には、自分が所属するスコープの外側のスコープがレキシカルスコープになります。

// レキシカルスコープ
let num = 0;

function fn() {
  // レキシカルスコープ
  let num2 = 1;
  
  console.log(num); // 0

  if (true) {
    console.log(num2); // 1
  }
}

グローバルスコープで宣言されている変数numと関数fn内で宣言されている変数num2は関数fnとその内部のif文から見た場合、レキシカルスコープとなるためアクセス可能です。

反対に、関数fn内で宣言されている変数num2は外部からアクセスする事は出来ません。

function fn() {
  let num2 = 1;
  
  console.log(num);

  if (true) {
    console.log(num2);
  }
}

console.log(num2); // エラー

このレキシカルスコープの存在がクロージャを理解するポイントとなります。

クロージャとは

クロージャとは、関数内で使用されている変数がレキシカルスコープの変数の値を保持し続けている状態です。通常、関数内で使用される引数や変数は関数の実行が終了すると、JavaScriptエンジンから不要とみなされてメモリから自動的に削除されます。

しかし下記のコードのようにレキシカルスコープの変数の値を使用している関数が戻り値として実行元に返された場合、その関数内の変数の値はメモリから削除されません

function factory(greeting) {
  function innerFn(name) {
    console.log(greeting + name);
  }

  return innerFn;
}

const hello = factory("こんにちは");
hello("太郎"); // こんにちは太郎
hello("花子"); // こんにちは花子

関数factoryの仮引数greetingに実引数「こんにちは」が渡されています。引数greetingを参照している関数innerFnが戻り値として実行元に返されるため、クロージャによって引数greetingの値が保持されます。

そのため、関数を何度も実行しても「こんにちは」という文字列が結果として表示されます。

クロージャを利用することで、グローバルスコープを汚染することなく変数や関数を宣言することが出来ます。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Closures

クロージャの実践例

クロージャの具体的な使用例を紹介します。

プライベート変数の定義

クロージャを利用して外部からのアクセスを防ぎつつ値を更新可能な変数・プライベート変数を作成出来ます。

function incrementFactry() {
  // この変数は関数の外からアクセス出来ません。
  let num = 0;

  function increment() {
    // 1を加算して再代入した値はクロージャによって保持されます。
    num = num + 1;
    console.log(num);
  }

  return increment;
}

const incrementNum = incrementFactry();
incrementNum(); // 1
incrementNum(); // 2
incrementNum(); // 3

incrementFactry関数の中に宣言されている変数numは関数の中からしかアクセス出来ません。通常の関数であれば、実行が完了すると宣言されている変数の値は削除されて次回の実行時に初期化されてしまいますが、関数の中でレキシカルスコープの変数へアクセスしている場合はクロージャによって変数の値を保持し続けられます。

変数numをグローバルスコープに宣言しても同じ結果を得られますが、値を変更されてしまう可能性があります。コード量が少ない場合はそこまで気にする必要はありませんが、Webサイトやアプリの運用の過程でコード量が増えてきた際に、意図せずに同じ名前の変数を定義して上書きされる可能性があるため、そのリスクヘッジとして対策が出来ます。

動的な関数の生成

クロージャを利用して動的な関数を作成出来ます。

function incrementFactry(num) {
  function increment(value) {
    // incrementFactry関数の引数numはクロージャによって値が保持されます。
    return num + value;
  }

  return increment;
}

const incrementNum = incrementFactry(10);
incrementNum(5); // 戻り値:15

const incrementNum2 = incrementFactry(20);
incrementNum(10); // 戻り値:30

incrementFactry関数の引数numがincrement関数の中で使用されているため、クロージャによって値が保持されます。

このようにクロージャを利用する事で渡す引数によって、戻り値が異なる挙動をする動的な関数を作成出来ます。

まとめ

クロージャについて解説しました。初心者にはなかなか理解するのが難しい概念ですが、当記事がクロージャの理解の参考になれば嬉しいです。

今後も追加事項や新たに分かった事があれば適宜記事を更新していく予定です。

Discussion