JavaScriptの動かないコード (初級編) switch文で,数値以外の変数を評価した時のエラー
以下のJavaScriptコードが意図した動作をしないのは,なぜですか。(制限時間1分)
やりたい事:
- 氏名を選択して,ボタンを押す。
- ⇒氏名に対応する苗字と,苗字に対応するメッセージが表示される。
<body> 氏名を選んでください。 <select id="hoge"> <option>鈴木</option> <option>鈴木 太郎</option> <option>鈴木 次郎</option> <option>佐藤</option> <option>佐藤 太郎</option> <option>佐藤 次郎</option> </select> <input type="button" onclick="f()" value="クリックしてメッセージを表示"> <script language="JavaScript"> function f(){ // セレクトボックス要素 var elem_select = document.getElementById( "hoge" ); // 選択されている氏名を取得 var str = elem_select.options[ elem_select.selectedIndex ].innerHTML; // 半角スペースのあとに名前が書いてあるか? if( str.match( /^.* .*$/ ) ) { // 半角スペースよりも前の部分を取り出し,苗字だけにする str = str.match( /^[^ ]*/ ); } // 苗字を表示する alert( "苗字は" + str ); // 苗字に応じてメッセージを表示 switch( str ) { case "鈴木" : alert( "日本で最も多い苗字です。" ); break; case "佐藤" : alert( "日本で2番目に多い苗字です。" ); break; default : // 「鈴木」でも「佐藤」でもない場合 alert( "エラー:異常値です。" ); } } </script> </body>
答え
発生する不具合
「苗字は〜〜」というアラートは,正常に表示される。
- 「苗字は鈴木」
- 「苗字は佐藤」
ここまではOK。
しかし,プルダウンで選んだ内容によっては,その次に
"エラー:異常値です。"
が表示される。
「鈴木」と「佐藤」を選んだ場合はOKだが,
「鈴木太郎」「鈴木次郎」「佐藤太郎」「佐藤次郎」を選んだ場合は,動作がおかしい。
理由
まず,matchの使い方がおかしい。
"abc".match( "a" )
の実行結果は,
"a"
という文字列型の変数ではなく,
[ "a" ]
という配列である。
「matchの返り値が文字列」と誤解していると思われる。
実際には,この関数は下記のような挙動をする。
>>> "abc".match("a") ["a"] >>> "abca".match("a") ["a"] >>> "abca".match(/a/) ["a"] >>> "abca".match(/a/g) ["a", "a"] >>> "123".match(/\d/) ["1"] >>> "123".match(/\d/g) ["1", "2", "3"] >>> "123".match(/\d*/g) ["123", ""]
熟読すべきURL:
JavaScript正規表現メモ。
http://d.hatena.ne.jp/koseki2/2009053...
- 全てのマッチした箇所を得る。gオプションを付けてString#matchを使う。
上記のような誤解をしていても,気付かないかもしれない。
JavaScriptは自動的に「型変換」を行なうからだ。
alert() は裏で型変換を行ない,引数を文字列に変換してからダイアログ上に表示する。
alertの引数が配列の場合は・・・
>>> ["a"].toString() "a"
このように,文字列になる。
それで,alertは,たとえ引数が配列だったとしても,正常に文字列を表示する事ができる。
なので,気付かない。
まるで「matchメソッドから文字列が返却された」かのように錯覚してしまう。
これはまずい。あとで,なし崩し的にコードが動作しなくなる可能性がある。
悪いサンプルの例:
JavaScript Reference
http://www.scollabo.com/banban/jsinde...
- matchメソッドの返り値をそのまま文字列とみなして操作している
型変換を自動的に行なってくれない例として,switch文が挙げられる。
switch文の変数評価の仕組みも知っておく必要がある。
switch文は,引数とcase句が「厳密に等しい」ことを要求する。
switch文
http://www.ajaxtower.jp/js/if/index4....
- 式と値が一致するかどうかは「==」演算子ではなく「===」演算子によって比較されます。この演算子の違いについては「等価演算子」を参照して下さい。
ECMAScript仕様書 switch文のBNF
http://www2u.biglobe.ne.jp/~oz-07ams/...
冒頭のコードで言うと,matchの返却値がArrayであるにも関わらず,switch側ではStringと比較している。
型を考慮した比較の場合,これだと決して等価にはならず,予期したcase部が実行されない事になる。
回避策
matchを利用した行の処理が配列ではなく文字列を返すよう,以下のように修正する。
// 半角スペースよりも前の部分を取り出し,苗字だけにする str = str.match( /^[^ ]*/ ); ↓ str = str.match( /^[^ ]*/ )[0];