クロージャーについて

クロージャーについて

JavaScript にはクロージャーという概念があり、初学者には難しい内容だと思います。

学習したことの振り返りもかねて、今回はクロージャーについてをまとめていきます。

クロージャーとは?

一言で表現するのが難しいのですが、クロージャーとは自分を囲むスコープにある変数を参照できる関数のことを言い、内側の関数が外側の関数の変数を参照できる機能を持っています。

言葉だけを聞いてもどういうこと???ってなりますよね(笑

これから順を追って解説していきます。

クロージャーを理解する上で必要な前提知識

まずクロージャーを理解する上で、スコープについて先に学んでおく必要があります。

スコープに関しては過去の記事で取り上げているのでこちらも確認してみてください。

クロージャーをつくってみる

冒頭でクロージャーの定義を確認しましたが、クロージャーとは自分を囲むスコープにある変数を参照できる関数のことでした。

その点を踏まえて、クロージャーを作成してみます。

function outFunc() {
  let value = 1; // <-- 自分を囲むスコープにある変数

  // ↓を参照できる関数
  function inFunc() {
    // 自分のスコープ内にvalueという変数が無いので
    // 外のスコープを探しに行く
    console.log(value);
  }

  // outFuncでは 内部で宣言した inFunc を返している
  return inFunc;
}

// outFuncを実行することで、inFuncが返ってきている
const fn1 = outFunc();

// 返ってきた inFunc が実行されている
fn1(); // 1
fn1(); // 1
fn1(); // 1

実行してみると、inFuncから見て外側の変数valueを参照できていることが分かります。

通常ローカル変数は関数が実行が終了すると外部から参照されないため、不要とみなされて破棄されますが、クロージャーはローカル変数を参照し続けることができます。

その動きをもっとわかりやすく確認するために、クロージャーで数値をカウントアップする関数を作成します。

function outFunc() {
  let value = 1;

  function inFunc() {
    ++value; // 実行するごとにoutFunc内のvalueの値がカウントされる
    console.log(value);
  }
  return inFunc;
}

const fn1 = outFunc();

// inFuncがvalueを参照し続けているため、値がインクリメントされる
fn1(); // 2
fn1(); // 3
fn1(); // 4

上記コードで、fn1()を数回実行しても変数valueを参照し続けているので、関数の実行後も変数 value が破棄されずに値がカウントアップされています。

この動作にはCall オブジェクトというものが関係してきます。

Call オブジェクト

Call オブジェクトとは、関数が実行された際に作成される見えない変数オブジェクトのことです。(アクティベーションオブジェクトとも呼ぶ)

argumentsthis引数その関数内のローカル変数の値親の Call オブジェクトの場所(アドレス情報)などが Call オブジェクトに格納されます。

この Call オブジェクトですが、関数宣言しただけではアクセスすることが出来ません。必ず関数を実行してやる必要があります。

実行時に生成されるCallオブジェクト
function myFunc(foo, bar){
  var val1, val2;
}

myFunc("val1", "val2"){
  /*
  実行時に生成されるCallオブジェクト
    arguments: ,
    this: ,
    foo: ,  // 引数1
    bar: ,  // 引数2
    val1: , // ローカル変数1
    var2:   // ローカル変数2
  */
}

先程のカウンターのコードをもう一度見てみましょう。

function outFunc() {
  // 2. スコープチェーンによって内側の関数inFuncから参照されている
  let value = 1;

  function inFunc() {
    // 1. ここのスコープにはvalueという変数が無いので、親のCallオブジェクトを探しに行く
    ++value;
    console.log(value);
  }
  return inFunc;
}

const fn1 = outFunc();

// inFuncがvalueを参照し続けているため、値がインクリメントされる
fn1(); // 2
fn1(); // 3
fn1(); // 4

内側の関数内に変数が無かった場合に、親の Call オブジェクトの場所の情報を辿っていくことで目的の変数を見つけることができます。この繋がりのことがスコープチェーンと呼ばれます。

クロージャーによって親の変数を参照し続けている事によって、関数の実行後もメモリから自動的に開放されることはありません。

クロージャーの用途

クロージャーは主に以下の用途で使われます。

  • 関数に状態を持たせる
  • 外から参照できない変数を作る
  • グローバル変数を作りたくない

グローバル変数を多様せず、変数のスコープを関数内に限定しておくことで予期せぬバグを発生させず保守性の高いコードを書くことができます。

また、クロージャーの性質を使うことで、柔軟に条件を判定できる関数を作成することができます。

function greaterThan(n) {
  return function (m) {
    return m > n;
  };
}
// 5より大きな値かを判定する関数を作成する
const greaterThan5 = greaterThan(5);
console.log(greaterThan5(4)); // => false
console.log(greaterThan5(5)); // => false
console.log(greaterThan5(6)); // => true

まとめ

  • クロージャーとは自分を囲むスコープにある変数を参照できる関数
  • クロージャによって、ローカル変数の状態を保持できる関数をつくれる
  • グローバル変数を作ることなく変数が管理できるので、予期せぬバグを発生させにくい
  • 関数実行時には Call オブジェクトという見えない変数オブジェクトが作成される

参考