JavaScript には本当に奇妙な落とし穴がたくさんあります。今日、文字列を数値に変換するために parseFloat を使っていたところ、渡された値を数値に変換できない場合は NaN(Not a Number)が返ることを知りました。そこで、次のようなコードで判定することにしました:
var score = parseFloat(prompt("Score:"));
if(score === NaN) {
console.log("Not a Number");
} else {
console.log(score);
}一見すると当然に見えるコードです。undefined や null のように、直接等値比較できそうに思えます。まさかまったく期待どおりに動かないとは思いもしませんでした。
JS には面白い点があります。NaN is Number(Not a Number is a Number)なのです。戻り値が undefined の場合は直接比較できます。undefined の型は undefined だからです。直感的には NaN も同じで、型は NaN だと思うでしょう。ところが調べてみると、NaN の型は実は Number なのです。次の比較をすると、どれもが false になります!
NaN == NaN "NaN" == NaN "Hi" == NaN
この罠は本当に深いです。解決策は難しくありません。isNaN 関数で判定すればよいのです。たとえば:
var score = parseFloat(prompt("Score:"));
if(isNaN(score)) {
console.log("Not a Number");
} else {
console.log(score);
}これで問題は解決したように見えますが、次の比較結果をよく見てください:
isNaN(NaN) == true
isNaN("hi") == true
isNaN(undefined) == true
isNaN(10) == false
isNaN("10") == falseisNaN は一見問題を解決しているように見えますが、NaN === NaN の判定ではないことに気づくでしょう。isNaN(“hi”) は true を返しますが、“hi” は文字列です。普通の意味では「“hi” は数値ではない」と言って問題ありませんが、厳密に言えば “hi” === NaN は false であるべきです。“hi” は文字列で、NaN は数値型だからです。この問題は、isNaN に引数を渡すとまず Number に変換されるために起こります。つまり Number(“hi”) === NaN を判定していることになり、Number(“hi”) 自体が NaN を返すのです。
「NaN だけが NaN である」という厳密な定義の isNaN がほしい場合は、Number.isNaN を使えます。次の比較結果を参照してください:
Number.isNaN(NaN) == true
Number.isNaN("hi") == false
Number.isNaN(undefined) == false