フロントエンドの現場で、フォームの入力値バリデーションやAPIから飛んできたレスポンスの数値チェックを実装していて、思わぬバグに頭を抱えた経験はないだろうか?
「あれ、この値ちゃんとチェックしたはずなのに、なんですり抜けたんだ?」
JavaScriptの型まわりの挙動、特に「数値かどうか」の判定は、長年多くのエンジニアを悩ませてきた魔窟の一つだ。今回は、その代表格であるグローバル関数の `isNaN()` と、ES2015で救世主として登場した `Number.isNaN()` の決定的な違いについて、ブラウザの裏側の動きも含めて徹底的に解説しよう。
中級からもう一歩抜け出して、後輩に「お、頼りになるな」と言わせるための実践的な知見を授けようと思う。
—
なぜJavaScriptの「数値判定」はこれほどややこしいのか?
JavaScriptは、動的型付け言語として非常にフレキシブルである反面、「良かれと思って裏側で勝手に型を変換してくれる(暗黙の型変換)」というお節介な性質を持っている。
このお節介が最も牙をむくのが、値が「NaN(Not-a-Number)」であるかを判定する瞬間だ。まずは、古くから存在するグローバルな `isNaN()` が裏側で何をやらかしているのか、その闇を覗いてみよう。
1. グローバルな `isNaN()` の正体:お節介な型変換おじさん
歴史的経緯もあり、グローバル空間に鎮座する `isNaN(value)` は、渡された引数が「数値ではない」かどうかを判定する。だが、その名前の裏でこっそりと恐ろしい処理を行っている。
それが、「まず引数を強制的に数値に変換する(ToNumber)」というステップだ。
// グローバルな isNaN の挙動
isNaN(123); // false (数値なので当然)
isNaN(‘123’); // false (!) 「おっ、文字列だけど数字に変換できるからセーフだな!」
isNaN(‘hello’); // true (‘hello’ は数値に変換できないので NaN)
「文字列の ‘123’ が `false`(つまり数値である判定)になるのは便利じゃないか」と思ったそこのあなた。実務の現場では、これがバグの温床になる。以下の例を見てほしい。
isNaN(undefined); // true
isNaN(null); // false (!) なぜなら null は数値に変換すると 0 になるから
isNaN(true); // false (true は 1 になる)
isNaN(false); // false (false は 0 になる)
isNaN(”); // false (空文字 ” も 0 になる)
isNaN(‘ ‘); // false (空白文字も 0 になる)
どうだろう? `null` やブール値、果ては「ただの空白スペース」までが、グローバルな `isNaN` にかかると「数値判定(`false`)」されてしまう。APIから送られてきた `null` や、ユーザーがうっかり入力した空白スペースを「よし、数値だな!」と誤認してしまうこの挙動、実務でどれだけ恐ろしいか想像がつくだろうか。
2. `Number.isNaN()` の正体:厳格なガードマン
こうした現場の悲鳴に応えるべく、ES2015 (ES6) で導入されたのが `Number.isNaN()` だ。
こいつのポリシーは極めてシンプルかつ厳格である。
「型変換なんて絶対にしない。渡された値がそもそも型として `Number` であり、かつその値が `NaN` である場合のみ `true` を返す」
これこそが、現代のフロントエンド開発における「真の数値判定」である。
// Number.isNaN の挙動(型変換を一切しない)
Number.isNaN(NaN); // true (当然本物の NaN)
Number.isNaN(123); // false (数値だけど NaN ではない)
Number.isNaN(‘123’); // false (文字列なので、変換せずに即座に false!)
Number.isNaN(undefined); // false
Number.isNaN(null); // false
Number.isNaN(‘hello’); // false (‘hello’ は文字列型なので、NaN かどうか以前に不合格)
見てほしい。`’123’` や `null`、`undefined` といった「非・数値型」の連中を、すべて華麗に `false` ではじき返してくれている。これこそが私たちが求めていた安全性だ。
—
そもそも `NaN` とは何か?(JavaScriptのバグが生んだ異端児)
ここで少し立ち止まって、JavaScriptにおける `NaN` という特殊な値の正体に触れておこう。
実は、JavaScriptの `NaN` は、IEEE 754という浮動小数点数の規格に基づいている。そして、JavaScriptにおいて非常に有名な仕様(あるいはバグに近い挙動)がある。
NaN === NaN; // なぜか false !
自分の頭がおかしくなったわけではない。JavaScriptでは、「どの `NaN` も他のどの `NaN` とも等しくない」という狂った仕様があるため、厳密等価演算子(`===`)を使って「この値は NaN か?」と確かめることができないのだ。
だからこそ、値が `NaN` であるかを正確に判定するには、専用の関数が必要になる。そして、その判定を「型変換なしで安全に行える唯一の存在」が `Number.isNaN()` なのである。
—
現場で使える!実践的なバリデーション・パターン
理屈は分かったところで、明日からの実務でそのまま使える安全なバリデーションの書き方を見ていこう。
ユーザーからの入力値(フォームのテキストボックスなど)は、基本的にすべて「文字列(String)」として取得される。これを安全に数値として扱い、かつバリデーションするコードの黄金パターンは以下の通りだ。
/
- ユーザー入力の文字列が「安全な数値」であるかを検証する関数
- @param {unknown} input – 検証する値
- @returns {boolean}
/
function isValidNumberInput(input) {
// 1. まず、値が文字列または数値であることを確認し、空文字や空白のみを除外する
if (typeof input !== ‘string’ && typeof input !== ‘number’) {
return false;
}
if (typeof input === ‘string’ && input.trim() === ”) {
return false;
}
// 2. Number() で明示的に数値に変換を試みる
const parsedNumber = Number(input);
// 3. Number.isNaN() を使って、変換結果が純粋な NaN でないかを厳格にチェックする
if (Number.isNaN(parsedNumber)) {
return false;
}
// 4. 必要に応じて、有限数(Finite)であるかもチェックする(Infinity対策)
if (!Number.isFinite(parsedNumber)) {
return false;
}
return true;
}
// — テストケース —
console.log(isValidNumberInput(“12345″)); // true: 安全な数値文字列
console.log(isValidNumberInput(12345)); // true: 数値
console.log(isValidNumberInput(” 123 “)); // true: トリムされて安全な数値
console.log(isValidNumberInput(“123a”)); // false: 途中に文字が混ざっている
console.log(isValidNumberInput(“”)); // false: 空文字
console.log(isValidNumberInput(null)); // false: null は弾かれる
console.log(isValidNumberInput(undefined)); // false: undefined も弾かれる
console.log(isValidNumberInput(Infinity));// false: 無限大は弾く
このコードのポイントは、「開発者が意図したタイミングで明示的に型変換(`Number(input)`)を行い、その後の判定には厳格な `Number.isNaN()` を使う」という役割分担にある。
グローバルな `isNaN()` に暗黙の型変換を裏側でこっそりやらせるのではなく、自分が何をしているのかをコード上で完全にコントロールする。これがシニアとしての作法だ。
—
まとめ:今日からコードレビューで意識すべきこと
最後に、チームメンバーに共有してほしいポイントをまとめる。
1. グローバルな `isNaN()` は、レガシーな互換性のために残されているものと心得よ。
うっかり使っている箇所を見かけたら、「それ、`null` や空文字をすり抜けませんか?」と優しくツッコミを入れてあげよう。
2. モダンなJavaScriptコードでは `Number.isNaN()` をファーストチョイスにせよ。
型変換を伴わないため、予期せぬバグを防ぎ、コードの意図が明確になる。
3. 数値のバリデーションは「明示的な変換 + 厳格な判定」のコンボで行う。
`Number()` や `parseFloat()` で変換しつつ、`Number.isNaN()` や `Number.isFinite()` でガードを固めるのが、堅牢なフロントエンドアプリケーションの基本だ。
JavaScriptの仕様の裏側を理解し、言語に踊らされるのではなく、言語を手の内でコントロールする。その積み重ねが、あなたを真のフロントエンド・スペシャリストへと押し上げてくれるはずだ。さあ、今すぐプロジェクト内の `isNaN` を検索して、リファクタリングを始めよう!

コメント