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

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

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

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

Q&A

解決済

4回答

562閲覧

実行結果の理由を知りたいです

javastudy

総合スコア9

JavaScript

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

0グッド

1クリップ

投稿2025/01/09 19:14

編集2025/01/10 03:20

下記のJavaScript1コードで、incrementFactory();と実行した際に、
incrementFactory()のreturnで関数aが実行されるということで、
各incrementFactory()が実行されるごとに、let num = 0;で、numが毎回初期化され、0になると思いますので、最終的な実行結果は、
1
1
1
1
となりました。

そして、JavaScript2コードでは、

const fn = incrementFactory(); fn(); fn(); fn(); fn();

を追記した場合の実行結果を見てみたのですが、

return a();ではエラーになり、return a;で正しく動作し、実行結果は、
1
2
3
4
となりました。

ここで2つの疑問となりますが、
なぜJavaScript1では、return a;では問題があり、return a();ではエラーが起きず、
JavaScript2では、return a;では問題がなく、return a();ではエラーが起きるのかの理由が分かりません。

以下は、JavaScript2で、return a();と書いた場合のエラーになります。
Uncaught TypeError: fn is not a function

また、もう一つの疑問ですが、
JavaScript2の以下のコードにつきまして、

const fn = incrementFactory(); fn(); fn(); fn(); fn();

↑このようにconst fnに incrementFactory();を格納しただけなのに、
なぜ、 let num = 0;の初期化は1回だけの実行という扱いにされ、
実行結果が1 1 1 1とならず、1 2 3 4となるのかという理論がわからないです。

よろしくお願いいたします。

JavaScript1

1 2function incrementFactory() { 3 4 let num = 0; 5 6 function a() { 7 num = num + 1; 8 console.log(num); 9 } 10 11 return a(); 12} 13 14incrementFactory(); 15incrementFactory(); 16incrementFactory(); 17incrementFactory(); 18

JavaScript2

1 2function incrementFactory() { 3 4 let num = 0; 5 6 function a() { 7 num = num + 1; 8 console.log(num); 9 } 10 11 return a; 12} 13 14const fn = incrementFactory(); 15 16fn(); 17fn(); 18fn(); 19fn(); 20 21

よろしくお願いいたします。

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

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

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

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

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

juner

2025/01/10 00:18

incrementFactory は () をつけて実行されるのに a は () をつけないで実行されるというのはどこの文書を参考にされておられまでしょうか?
javastudy

2025/01/10 02:56

コメントくださいまして、ありがとうございます。 今回は、参考先の情報につきまして、記載を控えさせてください。ごめんなさい。よろしくお願いたします。
guest

回答4

0

ベストアンサー

なぜJavaScript1では、return a;では問題があり、return a();ではエラーが起きず、
JavaScript2では、return a;では問題がなく、return a();ではエラーが起きるのかの理由が分かりません。

aa()の違いは理解されていますか?

aは、変数aに代入(束縛)されているオブジェクトを返します。ここでの「返す」は置き換わると考えてもいいです。

javvascript

1a = 1 2b = a # 2行目 3return a # 3行目

と書いたとき、2行目の右辺のaは、代入されているオブジェクトである1に置き換わり、bには1が代入されます。
3行目のreturnの対象であるaも同様に代入されている1に置き換わり、1が返ります。

変数に代入されるオブジェクトにはいろいろなものがありますが、この質問で重要なのは、関数そのものも変数に代入することができる点です。また、関数定義で名前として定義したものも変数として扱うことができます。以下の定義のときaは変数であり、その変数には定義した関数が代入されています。

javascript

1 function a() { 2 num = num + 1; 3 console.log(num); 4 }

なので、上の定義のあとに以下の処理をすると、

javvascript

1b = a # 1行目 2return a # 2行目

1行目ではa は定義した関数のものに置き換わり、bにもその関数が代入されます。
2行目では、aに代入されている関数がreturnされることになります。

さて、aに関数が代入されているとき、その関数を呼び出す(実行する)にはどのようにすればいいかというと、aの後に()を付けて、中に必要な引数を入れてやればいいのです。
ようするに、a()という記述はaに代入されている関数を呼び出す(実行する)ことを表わしています。また、そのとき、a()の部分は、aに代入されている関数を実行したときの返り値になります。

これらを踏まえて質問のコードを見てみましょう。
JavaScript1 では、incrementFactory関数はreturn a()となっているので、この関数は内部で定義している関数aを実行してその結果を返す関数です。 なので、コードでは続く行で

janvascript

1incrementFactory(); 2incrementFactory(); 3incrementFactory(); 4incrementFactory();

このように直接incrementFactoryを実行=後ろに()を付けて並べています。このときそれぞれの実行ごとに、価数のなかで関数aが実行されるので目的どおりの動作をします。(すべて1になる理由はほかの型の指摘どおり)

ここでincrementFactoryを書き換えてreturn aとするとどうなるでしょう。この関数は内部で定義している関数aそのものを返す関数になります。 関数aを実行していないことに注意が必要です。
このとき、コードの後半でincrementFactoryが4回実行されても、内部で関数aが実行されません。また、incrementFactoryそのものも呼ばれただけで、返り値をどこにも保存していないので捨てられてしまい、結局関数aは実行されることはありません。

JavaSc.pt2では、incrementFactory関数はreturn aとなっているので、この関数は内部で定義している関数aそのものを返す関数になります。
コードの後半は

javascript

1const fn = incrementFactory(); 2 3fn(); 4fn(); 5fn(); 6fn();`

このようになっています。
1行目で、incrementFactoryが実行され、返り値がfnに代入されます。 incrementFactoryの返り値は定義した関数aですから、変数fnにはその価数そのものが入ります。 後の行fn()として実行されていますが、fnに入っているのはaとして定義された関数ですのでそのとおり動作します。

ここでincrementFactoryを書き換えて、return a()とするとどうなるでしょう。この関数は内部で定義している関数aを実行してその結果を返す関数になってしまいます。関数aにはreturnがありませんので返り値が言語の仕様でundefinedという値とされています。
なのでそのように書き換えると、1行目でfnにはundefinedという値が入ります。そして、以降の処理でそれに()を付けて関数として実行しようとしたときに、undefinedは関数でなないので、「Uncaught TypeError: fn is not a function」(fnは関数ではありません)というエラーになるのです。


なぜJavaScript1では、その「状態を保持する」のルールは適用されないのでしょうか

クロージャというのは乱暴に言えば「環境が付きの関数」で、そのものがオブジェクトであって、関数定義時に作られます。 よく、コードそのものがクロージャだと捉えている人がいますが、そうではなく、数値や配列などのように、そのコードを元に作られるオブジェクトです。
質問のコードでは、関数incrementFactory(これもクロージャですが)の中で関数aが定義されています。関数aが定義が実行されると、関数aのオブジェクトは値が0の変数numが含んだクロージャになります。ここで重要なのは、関数aが関数incrementFactoryの中で定義されているため、incrementFactoryが呼ばれる度に新しい関数aが生成されるということです。関数のオブジェクトの概念を()を使って表わすと、関数aは生成されたときに(関数、num=0)のようなものになり、incrementFactoryが呼ばれるたびにこれが1つ生成されるということになります。

これを踏まえて、JavaScript1を見ると、以下のようになっています。

javascript

1incrementFactory(); 2incrementFactory(); 3incrementFactory(); 4incrementFactory();

JavaScript1の定義では、関数incrementFactoryは呼ばれると、(関数、num=0)が生成され、returnのタイミングで関数aが実行されます。 実行時のnumは0ですが1が足されて結果の1が表示されます。 incrementFactoryは4回呼ばれますから、(関数、num=0)が4回生成され、その度に関数aが呼ばれるので、1が4つ出力されます。

JavaScirpt2ではどうでしょう。

javascirpt

1const fn = incrementFactory(); 2 3fn(); 4fn(); 5fn(); 6fn();`

JavaScript2の定義では、関数incrementFactoryは呼ばれると、(関数、num=0)が生成され、return でそれが返されます。1行目でincrementFactoryが呼ばれて生成されて返された(関数、num=0)はfnに代入されます。
最初にfnが呼ばれて関数が実行されると、0だったnumに1が足され、結果の1が表示されます。 実行後のクロージャは(関数、num=1)になっています。次にfnが呼ばれると、fnは新しく定義されたりしていないので(関数、num=1)が実行され、2が表示され、結果としてクロージャは((関数、num=2)になります。

投稿2025/01/10 11:41

編集2025/01/11 03:53
TakaiY

総合スコア13907

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

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

javastudy

2025/01/10 19:27

ご回答くださいまして、誠にありがとうございます。 頂いております、みなさまの情報に目を通し、理解するのにお時間いただいております。 詳細で分かりやすいご説明で、非常に助けになっております。 今は、まだ理解が完全整理しきれておりませんが、今の時点で、以下の疑問がございます。 もしご都合が合いましたら、ご教授いただけましたら幸いです。 よろしくお願いいたします。 JavaScript1についてとなりますが、 こちらの「[JavaScript] 猿でもわかるクロージャ超入門」に記載の https://dqn.sakusakutto.jp/2009/01/javascript_4.html “クロージャを使うと、このように「状態を保持する関数」を作ることができます。” についてですが、こちらの「状態を保持する」の点を、考慮いたしますと JavaScript1もクロージャが使われている?のではないかとで、num = num + 1;のカウントアップの状態は保持され、 1 1 1 1の結果ではなく、1 2 3 4になるのではないかという疑問が出てきたのですが、 なぜJavaScript1では、その「状態を保持する」のルールは適用されないのでしょうか。
javastudy

2025/01/11 07:22

貴重なお時間を使ってご回答くださいまして、誠にありがとうございます。 みなさまのおかげで、無事、理論や知識が整理され、理解ができました。 非常に助かりました。 皆さんに助けられましたが、今回は、TakaiYさんのご回答をベストアンサーにさせていただければと思います。 「fnは新しく定義されたりしていない」、「incrementFactoryが呼ばれる度に新しい関数aが生成される」の部分で、さらに理解を進めることができました。 またご機会がございましたら、よろしくお願い申し上げます。
guest

0

incrementFactoryがaを返すのであればこう

javascript

1function incrementFactory() { 2 let num = 0; 3 function a() { 4 num = num + 1; 5 console.log(num); 6 } 7 return a; 8} 9 10incrementFactory()(); 11incrementFactory()(); 12incrementFactory()(); 13incrementFactory()();

投稿2025/01/10 00:08

yambejp

総合スコア116921

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

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

javastudy

2025/01/10 02:59

ご回答くださいまして、誠にありがとうございます。 無事解決いたしました。 頂きました回答に合わせまして、また追加の疑問が湧いてしまいました為、後ほど、質問欄に追記させていただきます。もしよろしければご教授の程、よろしくお願い申し上げます。
javastudy

2025/01/10 09:15

ご回答くださいまして、誠にありがとうございます。 まだ理解することができない部分があり、可能でございましたら、確認をさせてください。 incrementFactory( )( );という記述方法は、初心者の為、初めてみたのですが、 ( )を2つ付けますとどういう意味になるのでしょうか。 (関数を実行すると言いましたら、関数名incrementFactoryに( );をつけるということしかまだ知りません) よろしくお願いいたします。
yambejp

2025/01/10 09:32 編集

ユーザー関数incrementFactory()の中で宣言された関数をクロージャーといいます。 この場合a()がクロージャーにあたります。 https://developer.mozilla.org/ja/docs/Web/JavaScript/Closures 上記サイトのサンプルをご確認ください const myFunc = makeFunc(); とすることでmyFuncにdisplayName関数が設定され myFunc(); としてすることで実行しています これは makeFunc()()としているのと同等です。 ■参考 [JavaScript] 猿でもわかるクロージャ超入門 まとめ · DQNEO日記 https://dqn.sakusakutto.jp/2009/01/javascript_5.html
javastudy

2025/01/10 19:26

ご回答くださいまして、誠にありがとうございます。 頂いております、みなさまの情報に目を通し、理解するのにお時間いただいております。 猿でもわかる〜につきましても、拝見し、いくつかの疑問点が解決し、非常に助けになっております。 今は、まだ理解が完全整理しきれておりませんが、今の時点で、下記の疑問がございます。 もしご都合が合いましたら、ご教授いただけましたら幸いです。 よろしくお願いいたします。 JavaScript1についてとなりますが、 こちらの「[JavaScript] 猿でもわかるクロージャ超入門」に記載の https://dqn.sakusakutto.jp/2009/01/javascript_4.html “クロージャを使うと、このように「状態を保持する関数」を作ることができます。” についてですが、こちらの「状態を保持する」の点を、考慮いたしますと JavaScript1もクロージャが使われている?のではないかとで、num = num + 1;のカウントアップの状態は保持され、 1 1 1 1の結果ではなく、1 2 3 4になるのではないかという疑問が出てきたのですが、 なぜJavaScript1では、その「状態を保持する」のルールは適用されないのでしょうか。
javastudy

2025/01/11 07:21

貴重なお時間を使ってご回答くださいまして、誠にありがとうございます。 みなさまのおかげで、無事、理論や知識が整理され、理解ができました。 非常に助かりました。 皆さんに助けられましたが、今回は、TakaiYさんのご回答をベストアンサーにさせていただければと思います。 またご機会がございましたら、よろしくお願い申し上げます。 誠にありがとうございます。
guest

0

JavaScript1 の場合は incrementFactory が毎回実行されるため、毎回 num が 0に初期化されさらに a を実行しているので+1された結果が返ります。つまり何度やっても1が返ります

JavaScript2 の場合は incrementFactory が実行されるとnumが1回 0 で初期化され関数 a が返ります。
a の中でnumを初期化していないので、fn ( == a ) を何度実行しても num は初期化されずインクリメントされた値が返ります。

JavaScript1は下記と同義です

js

1function incrementFactory() { 2 let num = 0; 3 4 function a() { 5 num = num + 1; 6 console.log(num); 7 } 8 9 return a; 10} 11 12let fn = incrementFactory(); 13fn() 14fn = incrementFactory(); 15fn() 16fn = incrementFactory(); 17fn() 18fn = incrementFactory(); 19fn()

投稿2025/01/10 06:05

satoshih

総合スコア805

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

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

javastudy

2025/01/10 09:10

ご回答くださいまして、誠にありがとうございます。 まだ理解することができませんでした為、可能でございましたら、一つずつ確認をさせてください。 (私が記載の)JavaScript1では、なぜ、return a;という記述方法では問題があり、return a();という記述方法を使うのでしょうか。()の使い分けがよくわからないです。 よろしくお願いいたします。
satoshih

2025/01/10 09:42

a は関数そのもの(定義)で、 a()は関数aの処理を実行します。 javascript1で a() ではなくaを返すこともできますが、 その場合aの定義が返ってきているため、 処理をまだ実行できていないのです。 その戻り値aに対して()を付けて実行してあげると同様の結果が得られると思います。 javascript1でaを返した場合 incrementFactory()() や const fn = incrementFactory() fn() のような形になります
javastudy

2025/01/10 19:26

ご回答くださいまして、誠にありがとうございます。 頂いております、みなさまの情報に目を通し、理解するのにお時間いただいております。 ()()の件や、aとa()の理解につきまして、疑問点が解決し、非常に助けになっております。 今は、まだ理解が完全整理しきれておりませんが、今の時点で、以下の疑問がございます。 もしご都合が合いましたら、ご教授いただけましたら幸いです。 よろしくお願いいたします。 JavaScript1についてとなりますが、 こちらの「[JavaScript] 猿でもわかるクロージャ超入門」に記載の https://dqn.sakusakutto.jp/2009/01/javascript_4.html “クロージャを使うと、このように「状態を保持する関数」を作ることができます。” についてですが、こちらの「状態を保持する」の点を、考慮いたしますと JavaScript1もクロージャが使われている?のではないかとで、num = num + 1;のカウントアップの状態は保持され、 1 1 1 1の結果ではなく、1 2 3 4になるのではないかという疑問が出てきたのですが、 なぜJavaScript1では、その「状態を保持する」のルールは適用されないのでしょうか。
satoshih

2025/01/11 06:38

まずはクロージャが使われているかどうかではなく、 どこの部分の処理が行われているかを考えてみましょう。 JavaScript1は incrementFactory を毎回実行しているので ``` let num = 0; function a() { num = num + 1; console.log(num); } return a(); ``` 関数呼びだしなどを簡略化すると ``` let num = 0; num = num + 1; console.log(num); ``` が毎回実行されていて、 JavaScript2は関数 a が毎回実行されているので ``` num = num + 1; console.log(num); ``` が毎回実行されています こうしてみるとJavaScript1は何度やっても0で初期化されるため 1 しか出力されないですよね 対してJavaScript2は num が関数の外のスコープにあり初期化もされないため加算できます。
javastudy

2025/01/11 07:21

貴重なお時間を使ってご回答くださいまして、誠にありがとうございます。 みなさまのおかげで、無事、理論や知識が整理され、理解ができました。 非常に助かりました。 皆さんに助けられましたが、今回は、TakaiYさんのご回答をベストアンサーにさせていただければと思います。 またご機会がございましたら、よろしくお願い申し上げます。 誠にありがとうございます。
guest

0

Windows 11、Node.js 22.13.0で確認しました。

11行目ですが

return a;

だと関数aが呼ばれないので、

return a();

で上手く動作します。

投稿2025/01/09 20:06

hiroki-o

総合スコア1145

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

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

javastudy

2025/01/10 02:59

ご回答くださいまして、誠にありがとうございます。 無事解決いたしました。 頂きました回答に合わせまして、また追加の疑問が湧いてしまいました為、後ほど、質問欄に追記させていただきます。もしよろしければご教授の程、よろしくお願い申し上げます。
javastudy

2025/01/11 07:22

貴重なお時間を使ってご回答くださいまして、誠にありがとうございます。 みなさまのおかげで、無事、理論や知識が整理され、理解ができました。 非常に助かりました。 皆さんに助けられましたが、今回は、TakaiYさんのご回答をベストアンサーにさせていただければと思います。 またご機会がございましたら、よろしくお願い申し上げます。 誠にありがとうございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.34%

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

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

質問する

関連した質問