JS
1let increment=(function(){ 2 let count=0; 3 return function(){ 4 count+=1; 5 console.log(count); 6 } 7})(); 8 9increment(); //1 10increment(); //2 11increment(); //3 12
クロージャーについて学んでいるのですが
上記のコードにおいて
①即時関数にするのはなぜか
②return文にするのはなぜか
③let count=0で初期化されない(countの値が保持される)のはなぜか
教えて下さい
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答3件
0
ベストアンサー
即時関数にしなくてもいけますけど、使われもしない関数名が残るのが嫌で即時関数にするのでしょう。
インタープリタの気持ちになって処理を追ってみましょう。
まず、最初のfunction
で即時関数を作ります。
この時点では let count=0;
は実行されません。
変数increment
に代入するときの処理の最後の部分が ();
になっていて即時関数を呼び出しています。
この呼び出しによって let count=0;
を実行して変数count
が0
になり、その変数count
を参照する内側の即時関数functionを作って return し、その即時関数が外部変数count
を抱えた・閉じ込めた・クローズしたクロージャーとなって変数increment
に代入されます。
次に、increment()
でクロージャー(内側のfunction) を呼び出すと count+=1;
を実行して、外部変数countが 1
に変わり、return count;
で 1 が帰ります。
更に、increment()
でクロージャー(内側のfunction) を呼び出すと count+=1;
を実行して、外部変数countが 2
に変わり、return count;
で 2 が帰ります。
更に、increment()
でクロージャー(内側のfunction) を呼び出すと count+=1;
を実行して、外部変数countが 3
に変わり、return count;
で 3 が帰ります。
即時関数を名前付き関数にしてみました。
こうすることで、make_increment() を何度も呼び出せます。
make_increment() を呼び出すごとに 変数 count 領域と do_increment 関数オブジェクト が作られます。
javascript
1function make_increment () { // 関数定義により関数オブジェクトが作られる 2 let count; // 関数を呼び出すと変数領域が作られる 3 4 function do_increment () { // 関数定義により関数オブジェクトが作られる 5 count += 1; // do_increment() を呼び出したときに +1 される 6 console.log(count); 7 } 8 9 count = 0; // make_increment() を呼び出したときに代入する 10 return do_increment // 変数countを抱えた do_increment関数オブジェクトを返す 11} 12 13let increment = make_increment(); // make_increment関数を呼び出して do_increment 関数を作り increment変数に代入 14increment(); //1 15increment(); //2 16increment(); //3 17 18let increment2 = make_increment(); // increment とは別の count変数と do_increment関数を作る 19increment2(); //1 20increment2(); //2 21 22increment(); //4 : 別の変数であることの確認
投稿2020/01/15 07:51
編集2020/01/15 13:18総合スコア5406
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/01/15 08:41
2020/01/15 09:36
2020/01/15 10:01
2020/01/15 11:02 編集
2020/01/15 11:32
2020/01/15 11:42
2020/01/15 12:56
2020/01/15 13:01
2020/01/15 13:27
0
ご質問に示された「クロージャー」は、以下のコードと同義です。
(「①の回答」に繋がります)
var count=0; var increment=function(){ count+=1; console.log(count); return count; }
①即時関数にするのはなぜか
他のコードから変数 count
に触れさせない目的があり、即時関数のスコープに閉じ込めます。
(「③の回答」に繋がります)
②return文にするのはなぜか
increment()
として利用できるように、即時関数のスコープで宣言した関数を呼ぶためです。
③let count=0で初期化されない(countの値が保持される)のはなぜか
即時関数が実行されるとき(increment宣言時)にcount は初期化され、
即時関数のスコープ内で値を保持します。
追記)
クロージャは let
宣言が登場する以前(簡単に内容を書き換えられてしまう var
しか使えなかった時代)に、如何にして書き換えられないようにするか?といったトリックです。
上記の通り、スコープを変えることで達成しています。
let
が使えるモダンな環境では、使うことも無くなったように思いますが、古いコードを読むためにも知っておくべき内容となります。理解のポイントはスコープの違いとなります。
コメントを受けて追記)
Wikipedia クロージャ
即時関数は「関数宣言+実行構文(引数を受け付ける括弧)」で、無名である必要はありません。
// ご質問のコードを named関数 にした例 let increment=(function enclosure(){ let count=0; return function closure(){ count+=1; console.log(count); } })();
即時関数の実行は enclosureの実行ですので、その返却値(内部で関数宣言されたclosure)が、increment に代入されます(この代入で closure への参照となる)。
(この時点で即時関数の役割が消える、let count=0が参照されなくなる)
即時関数の役割は、enclosure内で宣言された変数とclosure関数の保持になります(メモリ上には残る)。
また、count は、直接参照されなくなりますが、closureを介して間接的に利用はできます。
内側の関数をクロージャにするために return文を使っているのですか?
increment に closure を参照させ、increment() を実行できるようにするための return です。
WikiPediaの説明のように、Javascript では 「関数の中で宣言される関数」はクロージャになりうるものです。
ちなみに、return を外すと、「Uncaught TypeError: increment is not a function」になりませんか?
さいごに
shiracamus さんの回答にもあるように、「コードを追いかけること」も大事になります。
投稿2020/01/15 08:19
編集2020/01/15 11:22総合スコア5432
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。