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

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

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

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

Q&A

解決済

4回答

2011閲覧

JavaScript、クロージャーにつきまして

Behemoth

総合スコア29

JavaScript

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

0グッド

3クリップ

投稿2018/06/23 01:46

編集2018/06/23 01:47

お世話になっております。
JavaScriptを勉強中のものです。

関数に関しての質問です。
「開眼!JavaScript」(オライリージャパン)101pにクロージャーの例として、下記のコードが挙げられていました。

var countUpFromZero = function(){ var count = 0; return function(){ return ++count; } }(); console.log(countUpFromZero()) //1 console.log(countUpFromZero()) //2 console.log(countUpFromZero()) //3

そこで、これを

var countUpFromZero = function(){ return function(){ return ++count; } }(); var count = 0;

と書き換えてみたところ、同じ挙動をしました。
スコープチェーンは関数が定義された時点で生成されるとのことですが、このばあい、countUpFromZeroによって返される無名関数がその後に定義される変数count を参照できるのはどうしてでしょうか?

どなたかよろしくお願いいたします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

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

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

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

HayatoKamono

2018/06/23 01:56 編集

回答ではないですが、参考として、次のキーワードのいずれかで検索してみて下さい。変数 巻き上げ、ホイスティング、hoisting
Behemoth

2018/06/23 02:47

ごヒント、ありがとうございます。まずはそこから理解を深めていきたいです。
guest

回答4

0

HayatoKamonoさんと同じく、ぱっと見は巻き上げと思いました。

var count = 0;前後のconsole.logの結果を見て下さい。
var宣言前のconsole.logが変数宣言前なのにエラーにならない(暗黙的に変数宣言されたものとして扱われる)のが巻き上げです

js

1var countUpFromZero = function () { 2 console.log(count); // undefind 3 var count = 0; 4 console.log(count); // 0 5return function(){ 6 return ++count; 7} 8}(); 9 10console.log(countUpFromZero()) //1 11console.log(countUpFromZero()) //2 12console.log(countUpFromZero()) //3

こちらはオライリーさんのJavascriptパターンP14 2.2.2巻き上げに記載あります。

投稿2018/06/23 02:38

編集2018/06/23 02:49
oikashinoa

総合スコア2826

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

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

Behemoth

2018/06/23 02:48

ご回答ありがとうございます。 var countUpFromZero = function(){ return function(){ return ++count; } }(); console.log(countUpFromZero()) //Nan var count = 0 こうすることで理解できたように思います。
guest

0

ベストアンサー

以下のソースを動かしながら考えてみて下さい。答えお待ちしてます。

…えらそ〜な事を書きましたが、自分も理解しきっていないかもしれないです。
有意識者の方、ツッコミお待ちしてます。

Q.なぜクロージャは、値を保持できるのか?

Q1:countUpFromZeroに何が入っていますか?
Q2:countUpFromZeroにはcountが無いからどこを参照していますか?
Q5:countUpでは、countの値が保持できないのは何故ですか?

Q.クロージャのメリットは?

Q3:参照しているQ2のcountのスコープはグローバルですか?関数スコープですか?
Q4:Q3のスコープだと、countUpFromZero関数以外からアクセスは出来ますか?

JS

1var countUpFromZero = function () { 2 var count = 0; 3 return function () { 4 return ++count; 5 } 6}(); 7 8console.log(countUpFromZero.toString()); 9console.log(countUpFromZero()); 10console.log(countUpFromZero()); 11//count++; 12console.log(countUpFromZero()); 13 14var countUp = function () { 15 var count = 0; 16 return (++count); 17}; 18 19console.log(countUp()); 20console.log(countUp()); 21//count++; 22console.log(countUp());

投稿2018/06/23 06:50

oikashinoa

総合スコア2826

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

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

Behemoth

2018/06/23 07:55

Q1 countUpFromZero には 無名関数と変数countが入っています Q2 無名関数が定義されたとき、"return ++count" のcountはcountUpFromZero内の変数countを参照する。そして無名関数が呼び出された際にもそれを参照し続けるのでcountがインクリメントされ続ける。 Q3 countのスコープは関数スコープ Q4できない Q5 ここが理解できていないところだと思います。countUpの場合、呼び出された際に"var count = 0"となっておいるため、毎回countの値が0に戻され、結果として1が返されるのでは、と考えました。 ただその理屈で行くと、countUpFromZeroにおいても毎回countの値は0に戻されてしまいます。 また、countUpFromZero.toString // function () { return ++count; } とのことですが、var count = 0はどこにいったのでしょうか?あまりに初歩的な質問でしたら、申し訳ありません。
oikashinoa

2018/06/23 08:40

Q1 countUpFromZero には 無名関数と変数countが入っています 以下の結果を見てみました? console.log(countUpFromZero.toString()); →count有りました? Q2 無名関数が定義されたとき、"return ++count" のcountはcountUpFromZero内の変数countを参照する。そして無名関数が呼び出された際にもそれを参照し続けるのでcountがインクリメントされ続ける。 正解です。関数宣言時にスコープチェーンが決定します。 countUpFromZero(内容はfunction (){return ++count;})が有る限り、countへの参照が続きます。 Q3 countのスコープは関数スコープ Q4できない 正解です。関数スコープなので関数より上のスコープ階層からは操作されなくなります。 逆にいうと、countを宣言している関数に内包されている子供な関数関数からは スコープチェーンにより参照可能です。 →countUpFromZero(内容はfunction (){return ++count;})にはcountが無いけど、  上位のスコープに有るcountを参照している。上位の関数は終了したけどcountへの参照は  続いているので、count(の値)は保持される。 Q5 ここが理解できていないところだと思います。countUpの場合、呼び出された際に"var count = 0"となっておいるため、毎回countの値が0に戻され、結果として1が返されるのでは、と考えました。 ただその理屈で行くと、countUpFromZeroにおいても毎回countの値は0に戻されてしまいます。 また、countUpFromZero.toString // function () { return ++count; } とのことですが、var count = 0はどこにいったのでしょうか?あまりに初歩的な質問でしたら、申し訳ありません。 実際の動きを見たほうが理解しやすいです。 1.Q1で何を間違っているか確認しましょう。 2.以下の行の各行に付けれるところはすべてブレークポイント付けてデバッグ実行してみて下さい。  `var count = 0で毎回countの値は0にならないのは何故?`について分かると思います。 ```JS var countUpFromZero = function parent() { var count = 0; return function childen() { return ++count; } }(); console.log(countUpFromZero()); ```
Behemoth

2018/06/23 23:49

ご返信が遅くなりました。 ご解説ありがとうございます。 Q5についてなのですが、 まず var countUpFromZero のparent関数が即時実行され、 var countUpFromZero = function children (){ return ++count; } になると思います。 var count = 0の下にブレークポイントをつけても、二回目以降は反応しないことを確認しました。 というこはparentのもっている関数スコープの変数countはどこかに隠ぺいされているものの、その存在を保っている、ということでしょうか?
oikashinoa

2018/06/24 02:26

> parentのもっている関数スコープの変数countはどこかに隠ぺいされているものの、その存在を保っている、ということでしょうか? countUpFromもcountUpZeroも関数抜けたのに、 countUpFromZeroだけcountの値が保持するのはなぜか? var countUp = function () { var count = 0; return (++count); }; countUp関数が終わるとcountを参照するものがないので、countは破棄される var countUpFromZero = function parent() { var count = 0; return function childen() { return ++count; } }(); countUpFromZero関数の中身はcountをreturnするchilden関数です。(countを参照する) だからparent関数にあるcount変数は、parent関数が終わっても破棄されません。(隠蔽されているわけではない) もしclassが分かるなら、以下と読み替えるとわかりやすいかな。 closure=メソッドが1つだけのclass ↓  var countUpFromZero = function constructor() { var count = 0; return function method() { return ++count; } }();
Behemoth

2018/06/26 09:54 編集

すみません、遅くなりました。 ご返信をしたと思い込んでおりまして... ご解説まことにありがとうございました。 多くのことを学ばせていただきました。 また、よろしくお願いいたします。
guest

0

ちなみにBehemothさんが書き換えた物(下記countUpFromZero_nonClosure)はクロージャの動作をしていません。以下を動かしてみると(エラーでて動かない所有ります。削ってみましょう)動きが解ると思います

js

1var countUpFromZero = function () { 2 var count = 0; 3return function(){ 4 return ++count; 5} 6}(); 7 8console.log(countUpFromZero()) //1 9console.log(countUpFromZero()) //2 10count++; // count is not defind 11console.log(countUpFromZero()) //3 12 13 14 15var countUpFromZero_nonClosure = function () { 16 return function(){ 17 return ++count2; 18 } 19}(); 20var count2 = 1; 21 22console.log(countUpFromZero_nonClosure()) //2 23console.log(countUpFromZero_nonClosure()) //3 24count2++;   //count2が関数外から変更できている 25console.log(countUpFromZero_nonClosure()) //4?5?試してみて下さい

投稿2018/06/23 02:58

oikashinoa

総合スコア2826

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

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

Behemoth

2018/06/23 03:08

回答、ありがとうございます! クロージャーのポイントは「関数スコープ内で宣言された変数を参照し続けること(生成された環境を記憶している)」にある、ということでしょうか? また、countUpFromZero_nonClosureは、クロージャーを生成するわけではなく、単なるcountという名前の変数の値をインクリメントする関数、ということですか?
oikashinoa

2018/06/23 06:47

理解しきれていない部分が有ると思いましたので、別回答でソースを書きました。 そちらを見て下さい。
Behemoth

2018/06/23 07:40

承知しました。ありがとうございます。
guest

0

「開眼!JavaScript」 良書ですよね。

P98 7.4スコープチェーン を見ましょう。

js

1var countUpFromZero = function(){ 2 return function(){ 3 return ++count; 4 } 5}(); 6var count = 0;    // ここの値を変えてみる

ここにcountが宣言されていないので、上のスコープの階層にcountを探しに行きます。


var count = 0;の値を変えたらどうなるかわかりやすいですよ。

投稿2018/06/23 02:25

編集2018/06/23 02:44
oikashinoa

総合スコア2826

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

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

Behemoth

2018/06/23 02:50

ありがとうございます。 私はまだ「二分開き」といった感じですが、頑張って内容をモノにしたいです。 教えていただいた部分をしっかりと確認しておきます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.49%

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

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

質問する

関連した質問