Ponz Dev Log

ソース派のシステムエンジニアの開発日記

JavaScriptのクロージャって結局なんだよ

JavaScriptでは初歩的なコードでは現れないけれどふとした拍子に出てくる単語っていくつかいるような気がします。 特にカリー化、高階関数クロージャ、ファンクターあたり。関数型プログラミングを進めていたら最初の方に出てきて戸惑ったのが クロージャ ってやつです。走り書きでn番煎じですが調べて噛み砕いた結果の備忘録です。

結論

要は 関数 です。("スコープ" と書いてあることが多いけど、実際に指しているのは関数の模様)

関数の中でも、以下の特徴を持つ関数のようですね。

  1. 未実行の関数を返す関数である。
  2. 子の関数からアクセスできる変数を保持する。

ただの関数とは何が違うわけ?

上の(1)だけ見てもJavaで言うところのファクトリメソッド, JavaScriptだと関数ファクトリ(?)っぽいです。 半分合っていそうですが、違いはソースコードを元に見た方が早い。

let outerVar = "outerVar";

function makeInner(params) {
  const innerVar = "InnerVar";

  function inner() {
    console.log(`I can see: ${outerVar}, ${innerVar}, ${params}`);
  }

  return inner;  // 未実行の関数を返す
}

const inner = makeInner("params");
inner();  // --> I can see: outerVar, innerVar, params

上の例だと、makeInner 関数がクロージャになります。inner 関数を返していますね。この makeInner 実行時には inner() としていないから未実行です。(定義1)

また、クロージャ内のローカル変数は inner 宣言時には関数のスコープ外になっていますが消えません。(定義2)

こうやって親の関数から返却される子の関数から、子&親の関数内/関数の引数/グローバルスコープの変数にアクセスできるところが他の関数とは違うところ。 ただ、変数は残ってはいるもののあくまで 参照のみが残っているだけ であって値がそのまま保持されるわけではなさそう。

(let outerVar ~ makeInnerクロージャの宣言まで同じ)

// クロージャから内部の関数を引数を渡して生成する前でも後でも参照する値は変更可能
// outerVar = "Outer2"; // inner() --> outerVar=Outer2
const inner = makeInner("params");
// outerVar = "Outer3"; // inner() --> outerVar=Outer3
inner();  // --> I can see: outerVar, innerVar, params

2番目の例の通り、途中で書き変わると最終的にクロージャから返却される関数の実行結果は変わるようですね。

参考

developer.mozilla.org

JavaScript関数型プログラミング 複雑性を抑える発想と実践法を学ぶ (impress top gear)

JavaScript関数型プログラミング 複雑性を抑える発想と実践法を学ぶ (impress top gear)