Jsのクロージャ(counter関数)の解釈

〽️ 関数は終わっちゃったけど‥ 環境はわすれないよ。



■Javascriptのクロージャが理解できない!そもそもHPにお決まりのように乗せられているcounter関数の意味がわからない‥ なんでこんな挙動をするのか。
そんな人達のわだかまりを解きたい!
世にクロージャの解説ページは多々ありますが、自分も今回

「わだかまり解放」に挑戦してみます。



ではお決まりのcounter関数。

//中身関数が入った外関数を定義  .....0
function Soto () {

         //変数countを宣言・初期化
         var count = 0 ;


         //中身関数を定義
         function Naka () {

                  //変数countの値を出力
                  console.log(count);

                  //countを増加
                  count++;

         }


         //中身関数を返す
         return Naka:

}

//宣言した変数にSotoの返り値を格納  .....1

         var Jikkou = Soto():

//Jikkouを実行(1回目)  .....2

         Jikkou();

//Jikkouを実行(2回目)  .....3

         Jikkou();

//Jikkouを実行(3回目)  .....4

         Jikkou();

■クロージャの解説ページによく出てくるcounter関数。外関数の内部で中関数を定義、返り値にセットします。そのあと新たに定義したグローバル変数に外関数を代入。そうすると、なぜかその変数を実行するたびにconsole.logした変数の値が増えていくというもの。



これがどうにも腑に落ちない。わだかまる。わだかまりにも程があ

〜とお思いの方もいらぴかりますよね。




◼︎外関数、中関数の内容はコードを読んで理解したつもり。

外関数
・変数countを宣言。  = 0で初期化
・中関数を定義
・中関数を返り値として返す


中関数
・変数countをconsole.logで出力
・変数countの値を1増やす

◼︎それなのに何故腑に落ちないのか!? わだかまるのか?!




〜地球。我らの星。火星。惑星だしまあわかる。あと水星とか土星とか月も。太陽。惑星でなくて恒星なんでしょ?  ではブラックホール‥ 意味がわからん!

◼︎もうひとつ考えることがあります。

 中関数の「環境」を考えてみる。


◼︎中関数の定義された時の「環境」。正式にはスコープチェーンなどと解説されています。

function Sotoのローカル変数であるcountが、中関数function Nakaにとってどのような存在であるのか。


まづ、

//変数countをconsole.logで出力
console.log(count);

◼︎この時、外関数のローカル変数countの値を参照して出力(console.log)しています。


そして、

//変数countをインクリメント
count++;

◼︎外関数のローカル変数countの値を操作しています。


この2点、

中関数が定義された時の環境

・外関数のローカル変数countの値を参照できる
・外関数のローカル変数countの値を操作(更新)できる

◼︎この「中関数の環境」を覚えておきながら、コードを詳しく見ていきましょう。







//中身関数が入った外関数を定義  .....0
function Soto () {

         //変数countを宣言・初期化
         var count = 0 ;


         //中身関数を定義
         function Naka () {

                  //変数countの値を出力
                  console.log(count);

                  //countを増加
                  count++;

         }


         //中身関数を返す
         return Naka:

}

 .....0
では外関数を定義しています。これは上記でさんざん触れたので大丈夫ですね。この外関数を実行すると中身関数Nakaが返されます。



//宣言した変数にSotoの返り値を格納  .....1

         var Jikkou = Soto():

 .....1
さてここで確認しておきたいのが、変数Jikkouには関数Sotoが代入されているのではなくて、あくまで
関数Sotoが実行された時の返り値、すなはち中関数Nakaが変数Jikkouに格納されるということ。



普通の関数でやってみると、

function hutuu(a,b) {
        return a+b;
}

var hensuu = hutuu(1,2)          //hutuuが実行されその返り値がhensuuに代入される
console.log(hensuu)          //3が出力される

◼︎これは直感的ですね。変数Jikkouに入っているのは外関数ではなく中身関数の実体です。⬇︎


         function Naka () {

                  //変数countの値を出力
                  console.log(count);

                  //countを増加
                  count++;

         }


なので、 以降変数Jikkouを実行したとしても中身関数以外の処理は行われることはありません。外関数の内容にあった、

         //変数countを宣言・初期化
         var count = 0 ;

         //中身関数を返す
         return Naka;

これらの処理は行われません。変数Jikkouを実行したとしても変数countが再び初期化されることはないのです。あくまで中身関数の内容である、


         function Naka () {

                  //変数countの値を出力
                  console.log(count);

                  //countを増加
                  count++;

         }


この部分のみが実行されます。

念を押すようですが外関数は既に終了しています。


ところで。

さっき例としてあげたhutuu関数の返り値は「数値」でしたが、今回の返り値は「外関数の内部で定義された中身関数」です。
これを変数に代入した場合、

「中身関数が定義された時の環境」がメモリ上に残されたまんまとなります。

「メモリ上」ってなんのメモリ?どこのメモリ?
〜そこは詳しく訊かないでください(筆者もそこまで調べていない)。

それよりも「中身関数が定義された時の環境」。以前お話しましたよね。

中関数が定義された時の環境

・外関数のローカル変数countの値を参照できる
・外関数のローカル変数countの値を操作(更新)できる

◼︎関数が終了したにもかかわらず、これがまだ生きています。どういうことなのか、次のコードを見ていきましょう。





//Jikkouを実行(1回目)  .....2

         Jikkou();

 .....2
変数Jikkouが実行されると、代入されていた関数Nakaが実行されます。

//代入されていた中身関数が実行される

         function Naka() {

                  console.log(count);

                  count++;

                 }

◼︎ふむふむ。実行されるのね。
ん???  ところが変数countは外関数のローカル変数なので、関数外部からアクセスできないはず。これではundefinedかなんかになるのでは???



 なりません。

 中関数の「環境」が残されているからです。





メモリ上(それが何かは詳しく訊かないでください)にこれ

中関数が定義された時の環境

・外関数のローカル変数countの値を参照できる
・外関数のローカル変数countの値を操作(更新)できる

が消えずに残っているんです。したがって本来外部からアクセスできないはずのローカル変数を参照したり操作したりできてしまうんです。

◼︎ではその中関数の処理を見ていきましょう。





console.log(count);


・メモリ上(詳しく訊かないでください)の変数countを参照できる環境にある

・現時点で変数countにセットされている値は0なので、0が出力される。

count++;


・メモリ上(詳しくきk)の変数countを操作できる環境にある

・+1された値が変数countに代入されメモリ上(くw)に残される。

◼︎容疑者たちがひとところに集まった場所で、主人公(名探偵)が事件の謎を解明しているところまで来ています。



◼︎変数jikkouを再び実行します。


//Jikkouを実行(2回目)  .....3

         Jikkou();

中身関数の処理を見てみましょう。

console.log(count);


・メモリ上(いでください)の変数countを参照できる環境にある

・1回目の実行で変数countに1がセットされているので、1 が出力される。



count++;


・メモリ上(きかな)の変数countを操作できる環境にある

・+1された値が変数countに代入されメモリ上(詳しくはwebで)に残される。  現状のcountの値である「1」がインクリメントされて「2」が残されます。

◼︎console.logされた値が0から1に増えました。その後count++;によって、謎のメモリ上の値も+1された2がsetされます。
さらにもう一度実行してみます。


//Jikkouを実行(3回目)  .....4

         Jikkou();

◼︎もうこの結果は想像がつきますね。出力は2、そして3がメモリ上にsetされます。




◼︎中身関数とその環境。この2つのセットが、コード全体の影響を受けないところで独立しているかのような。





此れ即ち、



◼︎ちなみにwikiにはこう書かれています。

クロージャ(クロージャー、英語: closure)、関数閉包はプログラミング言語における関数オブジェクトの一種。 いくつかの言語ではラムダ式や無名関数で実現している。 引数以外の変数を実行時の環境ではなく、自身が定義された環境(静的スコープ)において解決することを特徴とする。

 ※wikipediaさん引用させて頂きました。

◼︎「関数閉包」という言葉が使われていますね。関数とその環境をパッケージしている感じ。
「引数以外の変数」、上記のコードでは「count」に該当します。
「実行時の環境」、「jikkou」を実行したときを指しますね。
「自身が定義された時の環境」、中関数は外関数の内部で最初に定義された。
その時の「変数countを参照・更新できる」という環境。 外関数の終了以降であっても、この環境が保持されたまま中関数は実行されます。


 だから変数countの値が更新されてゆくのね♪



ぷしゅー ・・・


↑張りつめた風船がしぼむ音。
ようやくクロージャの正体まで辿り着きました。
counter関数のわだかまりを解く手ががりにはなったでしょうか?

創作活動の手助けとなれば幸いです。







「ふ」です。

swift、web、ガジェットなど。役立つ情報や観ていてたのしいページを書いていきたいと思います。

🐧 twitter 🐧