"BOKU"のITな日常

還暦越えの文系システムエンジニアの”BOKU”は新しいことが大好きです。

forループの中の「on」ブロック内のfunction()が正しく動かない/JavaScript・jQuery(備忘)

今回は、jQueryです。

イベントリスナループの罠・・とか言う人もいますが。

forループの中のonブロックに書いた処理が意図した通りに動かないという「よくある問題」のケース紹介と対処法を備忘のために書いておきます。

f:id:arakan_no_boku:20190302152643j:plain

 

問題の紹介

 

とりあえず、やろうとしたのは。

f:id:arakan_no_boku:20190302153155j:plain

これで「表示1」ボタンを押したら、「表示1」にする。

f:id:arakan_no_boku:20190302153245j:plain

「表示2」ボタンを押したら、「表示2」にする。

f:id:arakan_no_boku:20190302153332j:plain

それだけのことです。

こんな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はブロックスコープになので、何気に正しく動きます。

qiita.com

 

対処方法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); 
	    }
    });

ループカウンタを即時関数の引数にしてるのがミソです。

どちらの方法でも、いちおう、意図通りの動作にはなりました。

めでたし。

めでたし・・です。

ではでは。