今回は、jQueryです。
イベントリスナループの罠・・とか言う人もいますが。
forループの中のonブロックに書いた処理が意図した通りに動かないという「よくある問題」のケース紹介と対処法を備忘のために書いておきます。
問題の紹介
とりあえず、やろうとしたのは。
これで「表示1」ボタンを押したら、「表示1」にする。
「表示2」ボタンを押したら、「表示2」にする。
それだけのことです。
こんなHTMLでボタンとダイアログを定義します。
<body> <button id="btn_open1">表示1</button> <div id="modal1" class="modal"> <div id="modal_content1" class="modal_content"> <p>表示1</p> </div> </div> <button id="btn_open2">表示2</button> <div id="modal2" class="modal"> <div id="modal_content2" class="modal_content"> <p>表示2</p> </div> </div> </body>
これで普通のjQueryで書くとこんな感じで、これは意図通りに動きます。
$(function(){ $("#btn_open1").on('click', function() { $('#modal1').show(); }); $($('#modal1')).on('click', function(event) { if(!($(event.target).closest($('#modal_content1')).length)){ $('#modal1').hide(); } }); $("#btn_open2").on('click', function() { $('#modal2').show(); }); $($('#modal2')).on('click', function(event) { if(!($(event.target).closest($('#modal_content2')).length)){ $('#modal2').hide(); } }); });
でも、ボタンの数だけ処理を書くのは、どうもよろしくない。
そう考えて、1・2の部分を変数にしてループで定義すれば、ボタンが増えても簡単に処理が追加できると考えて、以下のようにします。
$(function(){ var arr = [1,2]; for(var key in arr){ $('#modal' + arr[key]).hide(); $("#btn_open" + arr[key]).on('click', function() { $('#modal' + arr[key]).show(); }); $($('#modal' + arr[key])).on('click', function(event) { if(!($(event.target).closest($('#modal_content' + arr[key])).length)){ $('#modal' + arr[key]).hide(); } }); } });
ところが、こうすると、どちらのボタンを押しても、同じダイアログしか表示されなくなる。
これが、今回の問題です。
原因と対処方法
jQuery(というか・・JavaScriptの・・だと思いますが)の仕様です。
forループの中にある「on」ブロックは、ループが全て終了してから実行されるので、意図した処理順になりません。
対処方法は2通りあって。
対処方法(1)
forループの変数定義で、varをletに変更するだけです。
$(function(){ var arr = [1,2]; for(let key in arr){ $('#modal' + arr[key]).hide(); $("#btn_open" + arr[key]).on('click', function() { $('#modal' + arr[key]).show(); }); $($('#modal' + arr[key])).on('click', function(event) { if(!($(event.target).closest($('#modal_content' + arr[key])).length)){ $('#modal' + arr[key]).hide(); } }); } });
for(let key in arr)の部分ですね。
関数スコープのvarと違い、letはブロックスコープになので、何気に正しく動きます。
対処方法2
ちゃんとした対処方法はこちらです。
ループブロックの中の処理を即時関数で囲ってやります。
$(function(){ var arr = [1,2]; for(var keya in arr){ (function(key){ $('#modal' + arr[key]).hide(); $("#btn_open" + arr[key]).on('click', function() { $('#modal' + arr[key]).show(); }); $($('#modal' + arr[key])).on('click', function(event) { if(!($(event.target).closest($('#modal_content' + arr[key])).length)){ $('#modal' + arr[key]).hide(); } }); })(keya); } });
ループカウンタを即時関数の引数にしてるのがミソです。
どちらの方法でも、いちおう、意図通りの動作にはなりました。
めでたし。
めでたし・・です。
ではでは。