\r\n```","answerCount":5,"upvoteCount":1,"datePublished":"2019-03-04T15:50:52.233Z","dateModified":"2019-03-04T15:50:52.233Z","acceptedAnswer":{"@type":"Answer","text":"イベント駆動の洗礼へよおこそ\r\n原因はJavaScriptのイベントとして登録された関数が何時発火するかにまだ理解が追いついて居ないからですね。\r\n\r\nJavaScriptはイベント置き場というものがあり、\r\nそこに「達成条件」と「実行して欲しい関数」をセットで登録します。\r\nイベント登録を受け付けたという情報だけ持って次の行へ進みます。\r\n\r\nJavaScriptの既存処理を全て完了し、暇になったら「達成条件を満たしたイベントはないかな?」と巡回を繰り返し、達成したイベントに紐付いている関数を実行します。\r\n(詳しくはイベントループで調べてください)\r\n\r\n```JavaScript\r\nfor (var i = 0; i < 2; i++) {\r\n setTimeout(function(){\r\n console.log(i + 1);\r\n }, 0);\r\n}\r\n// 3\r\n// 3\r\n```\r\n\r\n今回は`setTimeout(fn, 0);`として0ミリ秒後に実行するようにイベント登録を行いました。\r\nしかし、イベント登録を挟んでいる為に、イベント登録を行いfor文を抜ける方が優先されます。\r\nなので変数iのカウンターが終了条件を満たす2になってから、ようやく各関数が作動します。\r\n\r\n関数が作動したときに改めて変数iを確認すると…\r\nfor文を処理するのに増えてforループを抜けた時の値である`2`を読み取る事が可能です。\r\n結果、それに1を追加して3を画面表示することになりました。\r\n\r\n---\r\n\r\n解決策\r\n\r\n> 使ってしまう\r\n\r\n後で参照するから駄目なんですよ。\r\nその場で使ってしまうのが一番です。\r\n\r\n即時実行関数で包むのがわかりやすいでしょう。\r\n\r\n```JavaScript\r\nfor (var i = 0; i < 2; i++) {\r\n (function(index){\r\n setTimeout(function(){\r\n console.log(index + 1);\r\n }, 100);\r\n })(i);\r\n}\r\n// 1\r\n// 2\r\n```\r\n\r\n他にも関数は[Function.prototype.bind](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Function/bind)で\r\n引数を設定しながら関数発火だけを待たせるということも可能です。\r\n\r\n```JavaScript\r\nfor (var i = 0; i < 2; i++) {\r\n setTimeout(console.log.bind(null, i + 1), 100);\r\n}\r\n// 1\r\n// 2\r\n```\r\n\r\n> letを使う\r\n\r\n[let文](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/let)はES2015で追加された変数宣言時の構文です。\r\nletをfor文の中で使った場合、毎ループで変数スコープを作り保持されます。\r\n\r\n変数iが1の時にイベント登録された関数が変数iを呼び出すと、\r\nループを既に抜けてしまっておりi=2になっているにも関わらず、\r\n確実に1を取り出す事が可能になります。\r\n\r\n同様の問題で悩んだりハマる開発者がそれだけ多かったということですね。\r\n\r\n※IE11はletというキーワードを使っての変数宣言には対応していますが、このブロック絡みの仕様は対応しきれておらず動きません。\r\n\r\n> クロージャーでできそう\r\n\r\n同期処理の中でやってるので動きますね。\r\nちょっと試してみましょうか。\r\n\r\n```JavaScript\r\n// クロージャ版\r\nfor (var i = 0; i < 2; i++) {\r\n setTimeout((function(){\r\n var index = i + 1;\r\n return function(){\r\n console.log(index);\r\n }\r\n })(), 100);\r\n}\r\n// 1\r\n// 2\r\n```\r\n\r\n動きました。\r\nですがまぁ、クロージャーは覚える必要はそんなにないと思います。\r\n毎回こんな煩わしいこと書いてられませんしね。","dateModified":"2019-03-05T03:10:08.209Z","datePublished":"2019-03-05T03:10:08.209Z","upvoteCount":4,"url":"https://teratail.com/questions/177701#reply-264428"},"suggestedAnswer":[{"@type":"Answer","text":"for文が好きでない方のために一応別解\r\n※IE polyfill追記しました\r\n```javascript\r\n\r\n\r\n
\r\n\r\n
\r\n\r\n```","dateModified":"2019-03-05T03:03:12.726Z","datePublished":"2019-03-05T00:41:55.079Z","upvoteCount":3,"url":"https://teratail.com/questions/177701#reply-264380","comment":[{"@type":"Comment","text":"Array.prototype.fillはES2015で追加されたメソッドです。\r\nもちろんIE11は非対応、明示した方が良かったかもしれませんね。\r\nhttps://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/fill\r\n\r\n今回は質問者さんが無名関数を使っていましたし無名関数で良かったと思いますが、\r\n実践的にはアロー関数を使いまくるとか振り切った方が良いでしょうね。","datePublished":"2019-03-05T02:29:56.789Z","dateModified":"2019-03-05T02:29:56.789Z"},{"@type":"Comment","text":"miyabi-sunさん、ご指摘ありがとうござます\r\nつい中途半端に使いがちでした。\r\nMDNにはあまりprototypeにpolyfillするなとはありますが\r\nIE9以上対応版を追記してあります","datePublished":"2019-03-05T03:06:05.372Z","dateModified":"2019-03-05T03:06:05.372Z"},{"@type":"Comment","text":"これは私も最近気が付きましたね。\r\nえー、だったらES5じゃrange的に要素数の配列作れないじゃんって大ショックでしたね。","datePublished":"2019-03-07T06:44:30.276Z","dateModified":"2019-03-07T06:44:30.276Z"}]},{"@type":"Answer","text":"いわゆるクロージャーなら\r\n```javascript\r\n piyo.addEventListener('click', (function (a) { return function () { alert('クリック' + a)}})(i), false);\r\n\r\n```\r\nそういうのは、document で監視するべき。一行で、\r\n```javascript\r\ndocument.addEventListener('click',e=>alert(e.target.id),!1);\r\n```","dateModified":"2019-03-04T16:24:27.615Z","datePublished":"2019-03-04T16:24:27.615Z","upvoteCount":1,"url":"https://teratail.com/questions/177701#reply-264339","comment":[]},{"@type":"Answer","text":"`let`とブロックでいいと思います。\r\n```js\r\nfor(i=0;i<2;i++){ \r\n var piyo = document.getElementById('hoge' + i); \r\n {\r\n let j = i;\r\n piyo.addEventListener('click', function() { alert('クリック' + j);}, false);\r\n }\r\n}\r\n```","dateModified":"2019-03-04T16:05:11.586Z","datePublished":"2019-03-04T16:05:11.586Z","upvoteCount":1,"url":"https://teratail.com/questions/177701#reply-264338","comment":[{"@type":"Comment","text":"ああ、kei344さんの回答の方がいいと思います。","datePublished":"2019-03-04T16:07:22.454Z","dateModified":"2019-03-04T16:07:22.454Z"}]},{"@type":"Answer","text":"```js\r\n// for( i = 0 ; i < 2 ; i++ ) {\r\n// ↓\r\n for( let i = 0; i < 2 ; i++ ) {\r\n```","dateModified":"2019-03-04T15:55:28.091Z","datePublished":"2019-03-04T15:55:28.091Z","upvoteCount":3,"url":"https://teratail.com/questions/177701#reply-264336","comment":[]}],"breadcrumb":{"@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"https://teratail.com","name":"トップ"}},{"@type":"ListItem","position":2,"item":{"@id":"https://teratail.com/tags/JavaScript","name":"JavaScriptに関する質問"}},{"@type":"ListItem","position":3,"item":{"@id":"https://teratail.com/questions/177701","name":"jsのイベントリスナーの関数に変数を設定したい。"}}]}}}
質問するログイン新規登録

Q&A

解決済

5回答

1680閲覧

jsのイベントリスナーの関数に変数を設定したい。

kkkkoo

総合スコア17

JavaScript

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

1グッド

2クリップ

投稿2019/03/04 15:50

1

2

セレクトのid「hoge0」を変更すると「クリック0」
セレクトのid「hoge1」を変更すると「クリック1」
としたいのですが、どちらも「クリック1」になります。

いろいろ調べた結果、クロージャーでできそうな気がしますが、
やり方がわかりません。

<select id="hoge0"> <option value="1">1</option> <option value="2">2</option> </select> <br> <select id="hoge1"> <option value="3">3</option> <option value="4">4</option> </select> <script> for(i=0;i<2;i++){  var piyo = document.getElementById('hoge' + i);  piyo.addEventListener('click', function() { alert('クリック' + i);}, false); } </script>
fa11enprince👍を押しています

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

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

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

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

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

guest

回答5

0

ベストアンサー

イベント駆動の洗礼へよおこそ
原因はJavaScriptのイベントとして登録された関数が何時発火するかにまだ理解が追いついて居ないからですね。

JavaScriptはイベント置き場というものがあり、
そこに「達成条件」と「実行して欲しい関数」をセットで登録します。
イベント登録を受け付けたという情報だけ持って次の行へ進みます。

JavaScriptの既存処理を全て完了し、暇になったら「達成条件を満たしたイベントはないかな?」と巡回を繰り返し、達成したイベントに紐付いている関数を実行します。
(詳しくはイベントループで調べてください)

JavaScript

1for (var i = 0; i < 2; i++) { 2 setTimeout(function(){ 3 console.log(i + 1); 4 }, 0); 5} 6// 3 7// 3

今回はsetTimeout(fn, 0);として0ミリ秒後に実行するようにイベント登録を行いました。
しかし、イベント登録を挟んでいる為に、イベント登録を行いfor文を抜ける方が優先されます。
なので変数iのカウンターが終了条件を満たす2になってから、ようやく各関数が作動します。

関数が作動したときに改めて変数iを確認すると…
for文を処理するのに増えてforループを抜けた時の値である2を読み取る事が可能です。
結果、それに1を追加して3を画面表示することになりました。


解決策

使ってしまう

後で参照するから駄目なんですよ。
その場で使ってしまうのが一番です。

即時実行関数で包むのがわかりやすいでしょう。

JavaScript

1for (var i = 0; i < 2; i++) { 2 (function(index){ 3 setTimeout(function(){ 4 console.log(index + 1); 5 }, 100); 6 })(i); 7} 8// 1 9// 2

他にも関数はFunction.prototype.bind
引数を設定しながら関数発火だけを待たせるということも可能です。

JavaScript

1for (var i = 0; i < 2; i++) { 2 setTimeout(console.log.bind(null, i + 1), 100); 3} 4// 1 5// 2

letを使う

let文はES2015で追加された変数宣言時の構文です。
letをfor文の中で使った場合、毎ループで変数スコープを作り保持されます。

変数iが1の時にイベント登録された関数が変数iを呼び出すと、
ループを既に抜けてしまっておりi=2になっているにも関わらず、
確実に1を取り出す事が可能になります。

同様の問題で悩んだりハマる開発者がそれだけ多かったということですね。

※IE11はletというキーワードを使っての変数宣言には対応していますが、このブロック絡みの仕様は対応しきれておらず動きません。

クロージャーでできそう

同期処理の中でやってるので動きますね。
ちょっと試してみましょうか。

JavaScript

1// クロージャ版 2for (var i = 0; i < 2; i++) { 3 setTimeout((function(){ 4 var index = i + 1; 5 return function(){ 6 console.log(index); 7 } 8 })(), 100); 9} 10// 1 11// 2

動きました。
ですがまぁ、クロージャーは覚える必要はそんなにないと思います。
毎回こんな煩わしいこと書いてられませんしね。

投稿2019/03/05 03:10

miyabi-sun

総合スコア21553

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

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

kkkkoo

2019/03/07 06:40

詳しい説明をありがとうございます。
guest

0

for文が好きでない方のために一応別解
※IE polyfill追記しました

javascript

1<script> 2//IE polyfill 3if(!Array.prototype.fill){ 4 Array.prototype.fill=function(v){ 5 for(var i=0;i<this.length;i++) this[i]=v; 6 return this; 7 } 8} 9window.addEventListener('DOMContentLoaded', function(e){ 10 Array(2).fill(null).forEach(function(i,j){ 11 document.querySelector('#hoge'+j).addEventListener('click', function(e) { 12 alert('クリック' + j); 13 }); 14 }); 15}); 16</script> 17<select id="hoge0"> 18 <option value="1">1</option> 19 <option value="2">2</option> 20</select> 21<br> 22<select id="hoge1"> 23 <option value="3">3</option> 24 <option value="4">4</option> 25</select> 26<br> 27<select><--対象外--> 28 <option value="5">5</option> 29 <option value="6">6</option> 30</select>

投稿2019/03/05 00:41

編集2019/03/05 03:03
yambejp

総合スコア118164

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

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

miyabi-sun

2019/03/05 02:29

Array.prototype.fillはES2015で追加されたメソッドです。 もちろんIE11は非対応、明示した方が良かったかもしれませんね。 https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/fill 今回は質問者さんが無名関数を使っていましたし無名関数で良かったと思いますが、 実践的にはアロー関数を使いまくるとか振り切った方が良いでしょうね。
yambejp

2019/03/05 03:06

miyabi-sunさん、ご指摘ありがとうござます つい中途半端に使いがちでした。 MDNにはあまりprototypeにpolyfillするなとはありますが IE9以上対応版を追記してあります
miyabi-sun

2019/03/07 06:44

これは私も最近気が付きましたね。 えー、だったらES5じゃrange的に要素数の配列作れないじゃんって大ショックでしたね。
guest

0

js

1// for( i = 0 ; i < 2 ; i++ ) { 2// ↓ 3 for( let i = 0; i < 2 ; i++ ) {

投稿2019/03/04 15:55

kei344

総合スコア69643

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

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

0

いわゆるクロージャーなら

javascript

1 piyo.addEventListener('click', (function (a) { return function () { alert('クリック' + a)}})(i), false); 2

そういうのは、document で監視するべき。一行で、

javascript

1document.addEventListener('click',e=>alert(e.target.id),!1);

投稿2019/03/04 16:24

退会済みユーザー

退会済みユーザー

総合スコア0

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

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

0

letとブロックでいいと思います。

js

1for(i=0;i<2;i++){ 2 var piyo = document.getElementById('hoge' + i); 3 { 4 let j = i; 5 piyo.addEventListener('click', function() { alert('クリック' + j);}, false); 6 } 7}

投稿2019/03/04 16:05

Lhankor_Mhy

総合スコア37634

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

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

Lhankor_Mhy

2019/03/04 16:07

ああ、kei344さんの回答の方がいいと思います。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.29%

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

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

質問する

関連した質問