質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

Q&A

解決済

3回答

1963閲覧

クロージャーについて

Loreley

総合スコア9

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

0グッド

2クリップ

投稿2020/01/15 07:19

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ページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

oikashinoa

2020/01/15 07:47

過去のクロージャーに関する質問を見た方が良いですよ
guest

回答3

0

ベストアンサー

即時関数にしなくてもいけますけど、使われもしない関数名が残るのが嫌で即時関数にするのでしょう。

インタープリタの気持ちになって処理を追ってみましょう。
まず、最初のfunctionで即時関数を作ります。
この時点では let count=0; は実行されません。
変数increment に代入するときの処理の最後の部分が (); になっていて即時関数を呼び出しています。
この呼び出しによって let count=0; を実行して変数count0になり、その変数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
shiracamus

総合スコア5406

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

AkitoshiManabe

2020/01/15 08:41

初心者マークの方への回答としては難しい表現かもしれませんね。 ただ、私がクロージャを完全に理解したのは shiracamus さんが回答されたように、コードを追いかけたときだったように思います(var 宣言しかできなかった時代の古い話ですけど)。
Loreley

2020/01/15 09:36

回答ありがとうございます。つまり、 即時関数にすることによってlet count =0が実行される→returnがあることによって内側の関数がクロージャになる(let count=0が参照できなくなる)→increacement()を呼び出すことでreturn文の関数が実行されるという解釈であってますか? returnをとると1,1,1になってしまいます
shiracamus

2020/01/15 10:01

> let count=0が参照できなくなる いえ、外側の count を参照します。 変数宣言と変数代入処理を分けて考えてください。 countに0を代入するのは、外側の関数を呼んだ時です。 内側の関数を呼んだときは0の代入が行われません。increment() ではcount+=1;してcountの値を返すだけです。 あとで図を書いてみますね。
shiracamus

2020/01/15 11:02 編集

名前付き関数にしたソースコードを追記してみました。 ひとまず、これで処理を追ってみていただけませんか? count変数が呼び出す毎に作られるのも面白い結果だと思います。
shiracamus

2020/01/15 11:32

図を追記しました。
AkitoshiManabe

2020/01/15 11:42

shiracamus さん、わかりやすいw Loreley さん、この図を参考にコードを追ってみましょう。 エンクロージャを即時関数ではなく、make_increment とした例です。
Loreley

2020/01/15 12:56

わかりやすい図をありがとうございます。 increacement()を呼び出すときは⑦と⑧だけが行われているということですね。make increacementの返り値を do increacementにすることでlet increacementの内容がdo increacementという関数に置き換わっていて、increacementを呼び出すたびに 外側のスコープの let count=0の値も増えていくわけですね。
shiracamus

2020/01/15 13:01

はい。そういうことです。
Loreley

2020/01/15 13:27

お二人とも本当にありがとうございます。 なんとか理解できました。ベストアンサーは 図を載せてくださったshiracamusさんにしようと思います
guest

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
AkitoshiManabe

総合スコア5432

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Loreley

2020/01/15 10:25

回答ありがとうございます。 即時関数にすることでlet count=0になる。→return文の関数が変数increacementに代入される(この時点で即時関数の役割が消える、let count=0が参照されなくなる)→increacement()を呼び出すことでcountの値を保持したまま関数が実行される ということでしょうか。 また、内側の関数をクロージャにするために return文を使っているのですか?
Loreley

2020/01/15 12:05

今確認しましたがsyntaxerrorになってました。混乱させてすいません
guest

0

最初の function は一度だけ実行されて、count はその時 0 となります。increment には、二つ目の function が保存されて、increment() とコールすると、二つ目の function が実行され、クロージャーで参照した count がインクリメントされます。

投稿2020/01/15 07:37

mmaeda

総合スコア269

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問