NaN は特定の値ではなく、「数値として表せなかった」という印だからです。
計算が失敗した2つの結果が、同じ失敗だとは限りません。
だから比べても等しくなりません。これは JavaScript ではなく IEEE 754 が決めています。
0 / 0 と Number('あ') はどちらも NaN になりますが、同じ失敗ではありません。
仕様ではどう書かれているか
=== の中身は IsStrictlyEqual という手続きです。
数値同士のときは Number::equal を呼び、そこにこう書いてあります。
引数のどちらかが
NaNならfalseを返す。
つまり例外なく最初に落とされます。大小比較も同じで、NaN が入ると全部 false になります。
< と > と <= が全部 falseになります。「どちらでもない」が正しい答えなので、
否定しても真になりません。
ところが includes や Set では等しく扱われる
同じ NaN なのに、結果が変わります。
console.log(isNaN('あ'));
console.log(isNaN(undefined));
console.log(isNaN(''));
console.log(isNaN([]));比べ方が3種類あるからです。
| 比べ方 | 使っているところ | NaN と NaN |
+0 と -0 |
|---|---|---|---|
IsStrictlyEqual(===) |
=== / indexOf |
false | true |
| SameValueZero | includes / Set / Map |
true | true |
| SameValue | Object.is |
true | false |
NaN と -0 のどちらを区別したいかで、使う道具が変わります。
- 素直に比べたい …
=== - 「その値が入っているか」を見たい …
includes/Set(NaNも見つかる) -0まで見分けたい …Object.is
判定するには
=== で判定できないので、専用の関数を使います。
グローバルの isNaN() を使わない
名前がよく似ていますが別物です。isNaN() は先に数値へ変換します。
'あ' は NaN ではありません。変換したら NaN になっただけです。
'' と [] が false なのも同じ理由で、どちらも 0 に変換されます。
「これは NaN か」を知りたいなら Number.isNaN() を使ってください。
isNaN() が答えているのは「これは数値に変換できないか」という別の質問です。
よくある誤解
typeof NaN は 'number' ではない、と思っている
NaN は数値型の値です。「数値ではない(Not a Number)」という名前ですが、型は数値です。
型で弾くことはできません。値として判定する必要があります。
自分自身と等しくないことを利用する
NaN は自分自身と等しくない唯一の値です。この性質で判定する古い書き方があります。
たしかに NaN のときだけ true になります。ですが読んだ人が意図を読み取れません。
Number.isNaN(x) と書いてください。
どこで NaN が生まれるか
意図せず NaN になる場面はだいたい決まっています。
数値に変換できない文字列、undefined との計算、定義域の外。
入り口で弾くのがいちばん確実です。
文字列を安全に数値へ直す手順は
文字列を数値に変換する にまとめてあります。
同じ IEEE 754 が決めている小数の誤差については なぜ 0.1 + 0.2 は 0.30000000000000004 になるのか にあります。
配列から NaN を含む重複を消したいときの注意は
配列から重複を取り除く にまとめてあります。