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

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

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

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

Q&A

解決済

2回答

1349閲覧

Javascript、ループでaddEventListenerを繰り返すと、掛け違いのような挙動

sugoidaizu

総合スコア5

JavaScript

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

0グッド

0クリップ

投稿2023/04/14 05:56

編集2023/04/28 09:56

やりたいこと

htmlのセレクトボックスにて、選択肢が大量にある場合、階層的に選択できるようにしたいと考えました。1つ目のセレクトボックスで大分類を選択し、2つ目で中分類、3つ目で末端の項目を選択するイメージです。

イメージ説明

質問

下のコードで実現できたと考えていますが(logic="OK")、このままでは案件によってコードの書き換えが必要なので、汎用的にするため、配列で指定できるように改修しました(logic="NG")。しかし、期待していた動作になりません。

当方の理解がどのように間違っているのか。どう直せばいいか。ご教授いただければ幸いです。

当方なりの考察

ソースを「var logic="NG";」としたとき、1つ目のセレクトボックスを操作すると、console.logで「A/B/2」と出力して欲しいところ、「B/C/4」と出力されました。本来、2つ目のセレクトボックスのイベントで出力するログです。

addEventListenerの動作が、当方の期待と異なるのかも知れません。
値渡しや参照渡しと関係あるかと妄想しましたが、解決には至りませんでした。

動作確認方法

下のソースを保存して、ブラウザで開き、
A地域を選択し、B国を選択し、C都市を選択すると、絞り込みができると思います。

ソースが「var logic="OK";」のままで、1つ目のセレクトボックスの選択を変えると、
2つ目のセレクトボックスが影響受けますが、
ソースを「var logic="NG";」に変更し、1つ目のセレクトボックスの選択を変えると、
2つ目のセレクトボックスが影響受けなくなります。

ソースコード

html、javascript

1<!DOCTYPE html> 2<html lang="ja"> 3<body> 4<script> 5function shiborikomi(name1,name2,n){ 6console.log(name1+"/"+name2+"/"+n); 7 idx=document.getElementsByName(name1)[0].selectedIndex; 8 v1= document.getElementsByName(name1)[0].options[idx].value; 9 n1= document.getElementsByName(name1)[0].options[idx].text.trim().substr(0,n); 10 11 es= document.getElementsByName(name2)[0].getElementsByTagName("option"); 12 for(i=0;i<es.length;i++){ 13 n2=es[i].innerText.trim().substr(0,n); 14 if(n1==n2 && v1>0){ 15 es[i].style.display=''; 16 }else{ 17 if(document.getElementsByName(name2)[0].selectedIndex==es[i].index){ 18 document.getElementsByName(name2)[0].selectedIndex=0; 19 } 20 es[i].style.display='none'; 21 } 22 } 23 em= document.getElementsByName(name2)[0]; 24 const ev=new Event("change",{bubbles:false,cancelable:true}); 25 em.dispatchEvent(ev); 26} 27 28var logic="OK"; 29 30window.addEventListener("DOMContentLoaded",function(){ 31 if(logic=="OK"){ 32 name1= "A"; 33 name2= "B"; 34 name3= "C"; 35 shiborikomi(name1,name2,2); 36 shiborikomi(name2,name3,4); 37 document.getElementsByName(name1)[0].addEventListener("change",function(e){shiborikomi(name1,name2,2)}); 38 document.getElementsByName(name2)[0].addEventListener("change",function(e){shiborikomi(name2,name3,4)}); 39 } 40 if(logic=="NG"){ 41 var arr=[[2,"A","B"],[4,"B","C"]]; 42 for (var i=0;i<arr.length;i++) { 43 n=arr[i][0];parent_=arr[i][1];child_=arr[i][2]; 44 shiborikomi(parent_,child_,n); 45 } 46 for (var i=0;i<arr.length;i++) { 47 n=arr[i][0];parent_=arr[i][1];child_=arr[i][2]; 48 document.getElementsByName(parent_)[0].addEventListener("change",function(e){shiborikomi(parent_,child_,n)}); 49 } 50 } 51}); 52 53</script> 54<br>A地域 55<select name="A"> 56 <option value="">選択してください</option> 57 <option value="1" selected>01:欧米</option> 58 <option value="2">02:アジア</option> 59</select> 60<br>B国 61<select name="B"> 62 <option value="" >選択してください</option> 63 <option value="11" selected>0101:アメリカ</option> 64 <option value="12">0102:フランス</option> 65 <option value="21">0201:インド</option> 66 <option value="22">0202:中国</option> 67</select> 68<br>C都市 69<select name="C"> 70 <option value="">選択してください</option> 71 <option value="111" selected>010101:ニューヨーク</option> 72 <option value="112">010102:ロサンゼルス</option> 73 <option value="121">010201:ニース</option> 74 <option value="122">010202:パリ</option> 75 <option value="211">020101:デリー</option> 76 <option value="212">020102:バンガロール</option> 77 <option value="221">020201:北京</option> 78 <option value="222">020202:香港</option> 79</select> 80</body></html>

補足情報(FW/ツールのバージョンなど)

当方は、chromeで動作確認しました。

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

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

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

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

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

guest

回答2

0

ベストアンサー

js

1for (var i=0;i<arr.length;i++) { 2 n=arr[i][0];parent_=arr[i][1];child_=arr[i][2]; 3 document.getElementsByName(parent_)[0].addEventListener( 4 "change",function(e){shiborikomi(parent_,child_,n)}); 5}

この shiborikomi(parent_,child_,n) で参照される変数 parent_ child_ n はどこにも宣言されていませんから、グローバル変数です。change イベントが発生したとき、それ以前で最後に代入された値を参照します。for ループの1回目でイベントリスナが登録され、for ループの2回目で変数の値が更新され、その後で登録されたイベントリスナが呼ばれます。

変数宣言を適切に行ってローカル変数にすれば直ると思います。

js

1for (let a of arr) { 2 const n = a[0]; 3 const parent_ = a[1]; 4 const child_ = a[2]; 5 document.getElementsByName(parent_)[0].addEventListener( 6 "change", function(e) { shiborikomi(parent_, child_, n); }); 7}

投稿2023/04/14 06:14

編集2023/04/14 06:21
int32_t

総合スコア20663

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

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

sugoidaizu

2023/04/14 06:20

解決しました。ありがとうございます。
guest

0

ChatGPTが別解を教えてくれたので、書き置きます。

■before
document.getElementsByName(parent_)[0].addEventListener("change",
function(e){shiborikomi(parent_,child_,n)}
);

■after
document.getElementsByName(parent_)[0].addEventListener("change",
(function(p,c,n){return function(e){shiborikomi(p,c,n)}})(parent_,child_,n)
);

「beforeの場合、イベントリスナーが呼び出された時に、現在のスコープの parent_, child_, n の値を参照して shiborikomi 関数を呼び出します。この場合、全てのイベントリスナーで同じ parent_, child_, n の値が参照されてしまいます。つまり、最後に設定された値が全てのリスナーに適用されるということです。

afterの場合、即時関数によって parent_, child_, n を引数に渡し、それぞれの値を保存するためのクロージャを返します。イベントリスナーが呼び出された時には、そのイベントリスナーに対応するクロージャ内の値が参照されます。このようにすることで、各リスナーに対して異なる parent_, child_, n の値が保存されます。つまり、各リスナーが期待通りに動作するようになります。

以上のように、即時関数を用いたクロージャを使用することで、値のスコープを適切に制御できるため、予期せぬバグを防ぐことができます。」

投稿2023/04/28 00:54

編集2023/04/28 00:56
sugoidaizu

総合スコア5

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

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

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

質問する

関連した質問