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

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

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

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

Q&A

解決済

4回答

1313閲覧

即時関数についてです

oqqu

総合スコア7

JavaScript

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

0グッド

0クリップ

投稿2020/04/03 02:28

JavaScript

1const getNextRainbowColor = (function() { 2 const colors = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet']; 3 let colorIndex = -1; 4 return function() { 5 if(++colorIndex >= colors.length) colorIndex = 0; 6 return colors[colorIndex]; 7 }; 8})(); 9 //出力結果 10console.log(getNextRainbowColor()); //文1    //red  11console.log(getNextRainbowColor()); //orange 12console.log(getNextRainbowColor()); //yellow 13console.log(getNextRainbowColor()); //以下省略 14console.log(getNextRainbowColor()); 15console.log(getNextRainbowColor()); 16console.log(getNextRainbowColor()); 17console.log(getNextRainbowColor()); 18console.log(getNextRainbowColor()); 19console.log(getNextRainbowColor()); 20

質問です。
以下は私の考えです。
getNextRainbowColorを即時関数で宣言したときにcolorIndexの値は0になり、返り値はredになります。すると文1でgetNextRainbowColorは再び呼び起されたのでコンソールへの出力は配列colorsの次の要素であるorangeになるとおもいました。しかし、出力結果は上の通りです。
この考えの誤っている所を教えてほしいです。

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

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

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

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

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

yambejp

2020/04/03 02:37

質問がよくわからない、最初にgetNextRainbowColor()を したらいきなりorangeがでてくる想定ということですか?
oqqu

2020/04/03 02:46

最初にコンソールに出力されるのがorangeだと思いました。 分かりにくくてすみません!
guest

回答4

0

宣言時、即時関数はreturn文以降のfunction() {...を返すだけで、返した関数は一度も実行されていません。
console.logで呼ばれたときにはじめて実行されるので、1度目の実行結果であるredが出力されます。

投稿2020/04/03 02:47

sfwlma

総合スコア52

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

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

0

これはJavaScriptの即時実行関数を使った
クロージャーのテクニックの一つです。

とはいえ、ES2015以降のコードではわざわざ利用する必要はありません。
昔のコードをリファクタリングする際に読めれば損はないよ程度のものです。


JavaScriptは関数が第一級オブジェクトです。
関数を変数に代入、関数の引数や戻り値として渡せる…という性質を持ってます。

またJavaScriptには関数を宣言した場合スコープを作ります。
中で宣言した変数・定数を束縛して外からはアクセス出来ないようにします。

この2つの性質を活用して、
JavaやPHPでのプライベート変数を作ってしまおうというのが
クロージャーの目的です。


普段我々は関数を定義する時にこうやりますよね。

js

1const colors = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet']; 2let colorIndex = -1; 3const getNextRainbowColor = function () { 4 if(++colorIndex >= colors.length) colorIndex = 0; 5 return colors[colorIndex]; 6};

この時、colorscolorIndexは外のスコープ内でむき出しになっており、
他の変数宣言等と衝突する可能性があります。
colorsなんてよくある名称ですからね。

そこで、一回関数を用意して包む事でスコープを作ります。
これでcolorsとcolorIndexを内部のスコープに閉じ込める事に成功しました。

js

1function hoge () { 2 const colors = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet']; 3 let colorIndex = -1; 4}; 5 6hoge(); 7console.log(colors); 8// Uncaught ReferenceError: colors is not defined

この関数を実行した時、未実行の無名関数を戻り値として返してあげます。
実行前の無名関数が戻り値だって!?
JavaScriptの関数は第一級オブジェクトなので、引数に設定したり戻り値として持ち運べるんでしたね。

戻り値の関数を受け取って実行すると
作った関数のスコープに潜り込んで動作が開始されます。

そして見事、関数内部で宣言したcolorsにアクセス出来ました。

js

1function constColors() { 2 const colors = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet']; 3 let colorIndex = -1; 4 5 return function () { 6 return colors; 7 } 8} 9 10const getColors = constColors(); 11console.log(getColors()); // ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet']

道端の人全てに名前が振ってある小説とか読むの辛いですよね。

これはプログラムのコードでも同じことが言えます。
名前は必要最小限に振ってあげると可読性がよくなります。

何度も使う処理でないならば名前を削ってその場で実行してしまいましょう。
即時実行関数に加工して完成です。

js

1const getColors = (function () { 2 const colors = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet']; 3 let colorIndex = -1; 4 5 return function () { 6 return colors; 7 } 8})(); 9 10console.log(getColors()); 11// ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet']

おまけ

ES2015では正式にクラス構文が実装され、
クロージャーを使ったプロパティの隠蔽は非推奨です。

Javaにはオブジェクトのメンバー変数にプライベート属性を付けると、
外からそのメンバー変数を読み書きできなくなります。

同じ事をJavaScriptでもクロージャーを使って実現しようという発想は多くあり、
昔はこのようなコードを好んで書くエンジニアも居ました。
古い技術ブログやQiitaの記事を漁れば出てくるかもしれませんね。

これはオブジェクト指向言語のプライベート変数を再現したコードになります。
戻り値にオブジェクトを作ってメソッドのようにしているのがわかります。

js

1const newUser = (function () { 2 var name = ""; 3 var age = 0; 4 5 return function (n, a) { 6 name = n; 7 age = a; 8 return { 9 getName: function(){ return name; }, 10 getAge: function(){ return age; } 11 } 12 } 13})(); 14 15const user = newUser("taro", 18); 16console.log(user.getName()); // "taro" 17 18console.log(user.name); // undefined

しかしこの方法はgetNameやgetAge等という関数を大量に生成するので、
APIのJSONを解析してuserを1000件生成しましょうとなるとメモリの消費量がヤバイことになります。

なので、メモリ消費量を気にするならば、素直にprototype変数を使ったほうがよくて、
それを綺麗な形で包んでくれるクラス構文を使ったほうが良いのです。
JavaScriptはこのオレオレクラス構文がプロジェクト毎に存在しており、時にはバグの温床になったりする程でしたからね。

プライベート変数が存在しない問題も残っていますが、

同じくプライベート変数が存在しないPythonでは
もうプロパティの先頭文字を_にしてプログラミングする際は_で始まるプロパティを参照しないルールで実装すればよくね?
みたいな慣習なようで、JavaScriptも同様のスタイルのプロジェクトをよく見かけます。

投稿2020/04/03 03:10

編集2020/04/03 03:16
miyabi-sun

総合スコア21158

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

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

0

getNextRainbowColorを即時関数で宣言したときにcolorIndexの値は0になり

その時点では colorIndexの値は -1 のままです

説明のため、ご質問のコードで示された関数2つに closure, enclosure と命名します。

javascript

1const getNextRainbowColor = (function enclosure() { 2 const colors = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet']; 3 let colorIndex = -1; 4 return function closure() { 5 if(++colorIndex >= colors.length) colorIndex = 0; 6 return colors[colorIndex]; 7 }; 8})();

getNextRainbowColorを即時関数で宣言したときには enclosure 関数ブロック内の colors, colorIndex は初期化され(colorIndex は -1 のまま)、closure 関数が返却されます。
getNextRainbowColor は closure 関数と同じですので、getNextRainbowColor() として実行されたときに closure() の実行になります(文1のところで ++colorIndex は -1 から 0 になる)。

teratail の検索欄に「クロージャ」を入力して検索すると、更に知見が広がると思います。

投稿2020/04/03 03:05

AkitoshiManabe

総合スコア5432

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

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

0

ベストアンサー

こうすると認識しやすいかもしれません

投稿2020/04/03 02:49

yambejp

総合スコア114784

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

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

yambejp

2020/04/03 02:53 編集

const getNextRainbowColor = (function() { const colors = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet']; let colorIndex = -1; console.log(colorIndex); return function() { console.log(colorIndex); if(++colorIndex >= colors.length) colorIndex = 0; return colors[colorIndex]; }; })(); console.log("do..."); for(var i=0;i<3;i++){ console.log(getNextRainbowColor()); }
yambejp

2020/04/03 02:53

この即時関数はコールバックを返す処理なので 呼ばれてはじめてコールバック内の処理が始まります
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問