一から勉強させてください( ̄ω ̄;)

最下級エンジニアが日々の学びをアウトプットしていくだけのブログです。

クロージャについて

今回は、JavaScriptを勉強しているとよく聞く「クロージャ」について学んだことを書いてみたいと思います。


まずクロージャとはなにか??

ちょっと調べてみてなんとなくわかったことは、


・関数の中に関数を書く「入れ子構造」が大事っぽい。
・関数を抜けた後もローカル変数を保持したりできるっぽい。


でもいまいちよくわからん。。


で、さらにネットとか本とか調べてると、クロージャの定番みたいなコード例を見つけました。こんな風に関数を呼び出すたびに返る値が変わっていくのがクロージャの定番らしい!!

var fn = f();
fn(); //1
fn(); //2
fn(); //3


とりあえず上の例をイメージして、自分も書いてみました!!

function outer(){
    var a=0;
    function inner(){
        a++;
        console.log(a);
    }
    inner();
}
outer(); //1と表示される(・∀・)
outer(); //1と表示される (゚д゚;)
outer(); //1と表示される(゚д゚;)


まったく変化しねえ…

ということでさらに調べてみると、「これは関数のCallオブジェクトに関する理解が必要です」的な記述を発見。


JavaScriptでは関数を呼ぶたびにCallオブジェクトというオブジェクトが暗黙に生成される。
そしてローカル変数(引数のパラメータ変数も)はこのCallオブジェクトのプロパティであり、一般にローカル変数の生存期間は関数が呼ばれてから抜けるまでの間である。なぜなら、どのプロパティからも参照されていないオブジェクトは自動的に消滅するからである。


なるほど。つまりこの一般に…じゃないパターンがクロージャというわけですな。


そこで、さっき書いたコードでこの点を振り返ってみると、


まず関数outerを呼び出すと、そのCallオブジェクト(outer用)が生成される。
           ↓
outerの中にある関数innerに対応するCallオブジェクト(inner用)が生成される。
           ↓
innerを抜けると、Callオブジェクト(inner用)が消滅する。
           ↓
同じく、outerを抜けると、Callオブジェクト(outer用)も消滅する。
           ↓
outerのローカル変数、誰も参照できまへん。
           ↓
あとはこの繰り返し。


なんとなくさっきのコードがダメだった理由がわかってきました。
生成と消滅のプロセスをひたすら繰り返してただけだから、あんな結果になったんすね。


ちなみに、

参照元がなくなるとオブジェクトが自動で消滅することを、ガベージコレクション

入れ子の内側の関数のCallオブジェクトから外側の関数のCallオブジェクト、さらにグローバルオブジェクトとプロパティを探しに行く仕組みを、スコープチェーン。


というらしいです。


要するに
ガベージコレクションに巻き込まれないようにする!」、「ローカル変数を参照し続ける何かが残るようにする!」
これがクロージャの正体なのではないかと。


ってことでさらにネットと本を参考に書きなおしたのがこれ。

function outer(){
    var a=0;
     function inner(){ 
        a++;
        console.log(a); 
    }
    return inner; //内部で宣言した関数を返す。関数自体は呼び出さない。
}

//outerの返り値をfnに代入
var fn= outer();
fn(); //1と表示される(・∀・)
fn(); //2と表示される(・∀・)
fn(); //3と表示される(・∀・)

きた!!


この場合、

innerはreturnしているだけで呼び出しているわけではないので、関数innerのCallオブジェクトは生成されない。
               ↓
outerの返り値を別のグローバル変数fnに代入。
               ↓
fnを呼び出すと、outerのCallオブジェクトが生成される。(プロパティa, と関数を参照しているプロパティinnerが入っているイメージ)
また、innerが参照していた関数オブジェクトも呼び出される。
               ↓
このfnとinnerに参照されている、関数オブジェクトはouterのローカル変数aへの参照を持つ。(スコープチェーン)
               ↓
参照元であるfnはグローバル変数なのでグローバルオブジェクトに参照されている。つまりガベージコレクションで消えない。
そして参照元であるfnが存在する限り、innerが参照していた関数オブジェクトやouterのCallオブジェクトはガベージコレクションの対象外となるので、結果、値を保持する事ができる。


っていう感じでしょうか。


このように、

・関数innerを関数outerの外側から呼び出せる。
・関数outerのローカル変数aが関数outerの呼び出し後も生きている。

というのがクロージャの真髄なのだと思います。


ちなみにreturn inner;のところをinner();にすると、2回目のfn()のところでUncaught TypeError: undefined is not a function が出ます。

1回目outerの処理が完結した時点でouterのCallオブジェクトはこの世から消え去り、2回目以降の変数fnは参照元が見つけられず、undefinedになるのでしょうか。さらなる考察が必要です!!


クロージャって実際はアクセス制御とかに利用するものなのだと思いますが、長くなってしまったのでここまでにします。


今回はこちらの本をかなり参考にしました。図も載っててかなりわかりやすかった!感謝!!

パーフェクトJavaScript (PERFECT SERIES 4)

パーフェクトJavaScript (PERFECT SERIES 4)


小さなことからコツコツと。