質問: セレクトのid「hoge0」を変更すると「クリック0」 セレクトのid「hoge1」を変更すると「クリック1」 としたいのですが、どちらも「クリック1」になります。 いろいろ調べた結果、クロージャーでできそうな気がしますが、 やり方がわかりません。 ``` 1 2 3 4 ```
回答: イベント駆動の洗礼へよおこそ 原因はJavaScriptのイベントとして登録された関数が何時発火するかにまだ理解が追いついて居ないからですね。 JavaScriptはイベント置き場というものがあり、 そこに「達成条件」と「実行して欲しい関数」をセットで登録します。 イベント登録を受け付けたという情報だけ持って次の行へ進みます。 JavaScriptの既存処理を全て完了し、暇になったら「達成条件を満たしたイベントはないかな?」と巡回を繰り返し、達成したイベントに紐付いている関数を実行します。 (詳しくはイベントループで調べてください) ``` for (var i = 0; i < 2; i++) { setTimeout(function(){ console.log(i + 1); }, 0); } // 3 // 3 ``` 今回は`setTimeout(fn, 0);`として0ミリ秒後に実行するようにイベント登録を行いました。 しかし、イベント登録を挟んでいる為に、イベント登録を行いfor文を抜ける方が優先されます。 なので変数iのカウンターが終了条件を満たす2になってから、ようやく各関数が作動します。 関数が作動したときに改めて変数iを確認すると... for文を処理するのに増えてforループを抜けた時の値である`2`を読み取る事が可能です。 結果、それに1を追加して3を画面表示することになりました。 解決策 > > 使ってしまう > > > 後で参照するから駄目なんですよ。 その場で使ってしまうのが一番です。 即時実行関数で包むのがわかりやすいでしょう。 ``` for (var i = 0; i < 2; i++) { (function(index){ setTimeout(function(){ console.log(index + 1); }, 100); })(i); } // 1 // 2 ``` 他にも関数はFunction.prototype.bindで 引数を設定しながら関数発火だけを待たせるということも可能です。 ``` for (var i = 0; i < 2; i++) { setTimeout(console.log.bind(null, i + 1), 100); } // 1 // 2 ``` > > letを使う > > > let文はES2015で追加された変数宣言時の構文です。 letをfor文の中で使った場合、毎ループで変数スコープを作り保持されます。 変数iが1の時にイベント登録された関数が変数iを呼び出すと、 ループを既に抜けてしまっておりi=2になっているにも関わらず、 確実に1を取り出す事が可能になります。 同様の問題で悩んだりハマる開発者がそれだけ多かったということですね。 ※IE11はletというキーワードを使っての変数宣言には対応していますが、このブロック絡みの仕様は対応しきれておらず動きません。 > > クロージャーでできそう > > > 同期処理の中でやってるので動きますね。 ちょっと試してみましょうか。 ``` // クロージャ版 for (var i = 0; i < 2; i++) { setTimeout((function(){ var index = i + 1; return function(){ console.log(index); } })(), 100); } // 1 // 2 ``` 動きました。 ですがまぁ、クロージャーは覚える必要はそんなにないと思います。 毎回こんな煩わしいこと書いてられませんしね。