MDNを読みながらクロージャを理解する

3 min read読了の目安(約3500字

目的

JavaScriptを勉強しているとクロージャという概念にぶち当たります。
難しいです。
私もMDNや参考書、その他ネット上の記事を見ましたが理解するのに時間がかかりました(完全には理解できていないかもしれない)。
そこで、MDNを見ながら、自分のような初学者にとってクロージャの何が難しいのかを記しつつ、そういった部分の理解への足掛かりになりやすい記事を書きたいと思いこの記事を書きました。

クロージャとは?

MDNには以下のように説明されています。

クロージャは、組み合わされた(囲まれた)関数と、その周囲の状態(レキシカル環境)への参照の組み合わせです。言い換えれば、クロージャは内側の関数から外側の関数スコープへのアクセスを提供します。JavaScript では、関数が作成されるたびにクロージャが作成されます。
引用元:https://developer.mozilla.org/ja/docs/Web/JavaScript/Closures

・・・?

周囲の状態への参照の組み合わせ?

内側の関数から外側の関数スコープへのアクセスを提供?

難しいですね。

クロージャを理解するのに必要な知識

MDNのクロージャの記事を読むために前提となる知識があります。

それはスコープについてです。

スコープとは変数や関数を使える有効範囲のことを言います。

グローバルスコープローカルスコープなどの意味、そしてある関数スコープ内で変数を使用したい場合、そのスコープ内で宣言された変数か、そのスコープ外で宣言された変数しか使用できないということを理解できていればクロージャを理解できるのではないかと思います。

最初のコードを眺めてみる

MDNのクロージャのページに書かれているコードを引用します。
一緒に考えていきましょう。


function init() {
 var name = 'Mozilla'; // name は、init が作成するローカル変数

function displayName() { // displayName() は内部に閉じた関数
    alert(name); // 親関数で宣言された変数を使用
  }
  displayName();
}
init();

引用元:MDN Web Docs クロージャ

ここではinit関数を定義しています。
init関数がすることは以下です。

・ローカル変数nameを宣言し文字列Mozillaを代入
・displayName関数を定義
・displayName関数を実行

displayName関数がすることは以下です。

・displayName関数外のスコープであるinit関数スコープで宣言されたローカル変数nameを引数で受け取り、alertで表示する

このコードをJSFiddleなどで実行するとMozillaとアラート表示されると思います。

MDNでこのコードとともに説明していることは、あるスコープ内で変数を使用したい場合、そのスコープ内で宣言された変数か、そのスコープ外で宣言された変数しか使用できないということです。

今回の場合だとdisplayName関数スコープ内で、外のスコープであるinit関数スコープで宣言されたローカル変数nameを使用していますね。

2番目のコードを眺めてみる

function makeFunc() {
  var name = 'Mozilla';
  function displayName() {
    alert(name);
  }
  return displayName;
}

var myFunc = makeFunc();
myFunc();

引用元:MDN Web Docs クロージャ

ここではmakeFunc関数を定義しています。
先ほどのinit関数との違いは以下です。

・displayName関数を実行するのではなく返していること

makeFunc関数を定義後はグローバルスコープで変数myFuncを宣言、makeFunc関数を実行して変数myFuncにdisplayName関数を代入しています。
最後に変数myFuncを関数呼び出ししてdisplayName関数を実行しています。

結果的にはinit()関数を実行した時と同じようにMozillaとアラート表示されることになります。

ここでのポイントは、変数myFuncはmakeFunc関数の外で実行されたのに、makeFunc関数スコープのローカル変数であるname変数の内容が表示できている点です。

あれ??
と思いませんか。

先ほど私は
あるスコープ内で変数を使用したい場合、そのスコープ内で宣言された変数か、そのスコープ外で宣言された変数しか使用できない
と書きました。

しかしここではグローバルスコープ上でmakeFunc関数スコープ内で宣言されたローカル変数nameを使用できています。
つまり、あるスコープで宣言された変数をそのスコープ外から使用しているということですね。

これは何故可能なのか?

それはdisplayName関数がmakeFunc関数の中で作られた関数であるため、その環境(ローカル変数nameが使える)を保持しているからです。

このように、ある関数が作られた時にその環境が関数の中で保持されていることクロージャと言います。

・・・

まだピンとこないかもしれません。

ある関数が作られた時にその環境を関数の中で保持するということをよりイメージしやすいコードが次のコードです。

3番目のコードを眺めてみる

function makeAdder(x) {
  return function(y) {
    return x + y;
  };
}

var add5 = makeAdder(5);
var add10 = makeAdder(10);

console.log(add5(2)); // 7 と表示される
console.log(add10(2)); // 12 と表示される

引用元:MDN Web Docs クロージャ

クロージャの性質を利用すると動的な関数を作ることができます。
動的な関数とは何でしょうか?

コードを見てみましょう。
このmakeAdder関数がまさに動的な関数を作ることができる関数です。

makeAdder関数の引数に5を渡している時には 5 + y を返す関数が作られ、グローバル変数add5に代入される。引数に10を渡している時には 10 + y を返す関数が作られ、グローバル変数add10に代入される。

このmakeAdder関数のように、引数に渡す値によって内部処理が変わってくるなど、状況によって異なる関数を作る関数を動的な関数と言います。

ここのどの部分にクロージャが関わっているか?

add5関数を作った時にはローカル変数xに5が保持され、add10関数を作った時にはローカル変数xに10が保持されています。

つまり、動的な関数は、ある関数が作られた時にその環境が関数の中で保持されるというクロージャの性質を利用して作られているということです。

まとめ

・クロージャとは、ある関数が作られた時にその環境が関数の中で保持されていること。
・クロージャを使えば、あるスコープで宣言された変数をそのスコープ外から使用することができる。
・クロージャを使えば、動的な関数を作れる。
・MDNを読むのは難しい。