【テクニカル・上級編】 NaNの判定手法(isNaN vs Number.isNaN) – JavaScript実践ガイド

こんにちは。フロントエンドの現場で日々、不可解なバグの温床となる「型」の揺らぎと格闘しているエンジニアの皆さん。

JavaScriptにおける「数値」の扱いは、いつの時代も私たちを悩ませてきた。IEEE 754倍精度浮動小数点数という、限られたビット列の中に宇宙を表現しようとした代償として、この言語には `NaN`(Not-a-Number)という、名前の通り「数ではない数」が幽霊のように漂っている。

今日は、この `NaN` の判定において、私たちがなぜ長年苦しめられてきたのか、そしてES6以降のモダンなJavaScript開発において、グローバルな `isNaN()` を即座に捨てて `Number.isNaN()` へ移行すべき理由を、ブラウザエンジンの内部挙動や実務でのアーキテクチャの観点から徹底的に解き明かしていこう。

—

1. そもそも `NaN` とは何者か?(V8エンジンの視点)

まず大前提として、`NaN` の奇妙な性質を理解しておかなければならない。JavaScriptにおいて、`NaN` は `typeof NaN` が `”number”` を返すという悪名高いジョークを持っている。

V8などの近代的なJavaScriptエンジンは、パフォーマンス最適化のために「Smis(Small Integers)」や「HeapNumbers」といった数値表現を動的に切り替えている。計算結果が破綻したとき、あるいは数値化不可能なパースが行われたとき、エンジンは IEEE 754 の「指数部がすべて1で、仮数部が非ゼロ」というビットパターンを `NaN` として割り当てる。

そして、IEEE 754 の仕様上、`NaN` は自分自身を含め、いかなる値とも等しくないという残酷な鉄則がある。

console.log(NaN === NaN); // 嘘だろ…?と叫びたくなる `false`

この「自己不一致性」があるがゆえに、私たちは単に `value === NaN` と比較してバグを踏み抜くという、ジュニアエンジニアが必ず通る洗礼を受けることになる。

—

2. 悪夢の入り口:グローバルな `isNaN()` の正体

この厄介な `NaN` を判定するために、JavaScriptの黎明期から用意されていたのがグローバル関数である `isNaN()` だ。しかし、この関数こそが、多くのコードベースを静かなる破滅へと導いてきた。

グローバルな `isNaN()` の最大の罪は、「暗黙の型変換(Type Coercion)」を勝手に行うことにある。

// グローバルな isNaN の挙動
console.log(isNaN(NaN)); // true
console.log(isNaN(“hello”)); // true (えっ?)
console.log(isNaN(undefined)); // true (なんでやねん)
console.log(isNaN({})); // true (オブジェクトだぞ?)
console.log(isNaN(123)); // false
console.log(isNaN(“123”)); // false (文字列だけど数値に変換できるからセーフ)

何が起きているのか?

グローバルな `isNaN(val)` は、渡された引数が `Number` 型でない場合、内部的にまずそれを `Number(val)` で数値に変換しようと試みる。

1. `”hello”` を数値に変換しようとする $\rightarrow$ `Number(“hello”)` は `NaN` になる。したがって `isNaN(“hello”)` は `true` を返す。
2. `undefined` を数値に変換しようとする $\rightarrow$ `Number(undefined)` は `NaN` になる。したがって `isNaN(undefined)` は `true` を返す。
3. 空文字 `””` や真偽値 `true`(`1`に変換される)なども、意図しない型変換の魔力に巻き込まれる。

この仕様により、APIレスポンスのバリデーションやフォームの入力値チェックにおいて、文字や未定義値を誤検知し、アプリケーションが予期せぬ挙動を起こすバグが無限に生産されてきた。型の安全性を担保したいモダンなSPAアーキテクチャにおいて、この「おせっかいな暗黙の変換」は百害あって一利なしである。

—

3. 救世主:ES6 `Number.isNaN()` の厳格な世界

この惨状を見かねて、ECMAScript 2015 (ES6) で導入されたのが `Number.isNaN()` だ。

このメソッドは、私たちが長年求めていた「純粋な `NaN` の判定」を完璧にこなしてくれる。余計な型変換は一切行わない。渡された値が 「厳密に `Number` 型であり、かつその値が `NaN` であるか」 だけを評価する。

// Number.isNaN の高潔な挙動
console.log(Number.isNaN(NaN)); // true

// 以下はすべて false (型変換しないため、安全!)
console.log(Number.isNaN(“hello”)); // false
console.log(Number.isNaN(undefined)); // false
console.log(Number.isNaN(null)); // false
console.log(Number.isNaN({})); // false
console.log(Number.isNaN(“123”)); // false

なぜこれが堅牢なアプリケーションに必須なのか?

実務レベルのフロントエンド開発、例えば複雑なダッシュボードのグラフ描画や、リアルタイムの株価・ECのカート計算において、データが汚染されている(汚染されたAPIペイロードや不正なユーザー入力)ことは日常茶飯事だ。

もしグローバルな `isNaN` を使っていると、APIからたまたま混入した `null` や文字列が意図せず数値演算のエラーフローに引きずり込まれ、レンダリングエンジンのクラッシュや、非同期処理の致命的な競合を引き起こす。

`Number.isNaN()` を採用することは、防衛的プログラミング(Defensive Programming)の第一歩であり、「型が崩れたデータを無駄に解釈しない」という堅牢なアーキテクチャの礎となる。

—

4. 実務で使えるポリフィルとカスタム・ガーディアン関数

古いレガシーブラウザ(IE11など……もう駆逐されているはずだが、組み込み環境などではまだ生きている)をサポートする必要がある場合や、より厳密な型安全をコードベースに強制したい場合、以下のようなユーティリティ関数をアーキテクチャの基盤層に組み込むことを推奨する。

/

  • 堅牢なNaN判定ユーティリティ
  • 意図しない型変換を完全に排除し、厳密にNaNのみを検知する
  • @param {unknown} value – 評価する値
  • @returns {boolean}

/
const isStrictNaN = (value) => {
// 1. まず Number.isNaN が使える環境であればそれを直接使う(モダンブラウザ)
if (typeof Number.isNaN === ‘function’) {
return Number.isNaN(value);
}

// 2. レガシー環境用の厳密なフォールバック
// 「typeofがnumberであり、かつ自分自身と等しくない」という数学的性質を利用
return typeof value === ‘number’ && value !== value;
};

// — 使用例 —
console.log(isStrictNaN(NaN)); // true
console.log(isStrictNaN(“NaN”)); // false (文字列なので安全にスルー)
console.log(isStrictNaN(undefined)); // false

このアプローチを取ることで、JavaScriptエンジンの最適化パス(V8のHidden ClassやInline Cachingなど)を阻害することなく、予測可能で高速な評価を実現できる。

—

5. チーフアーキテクトからの提言

JavaScriptは、その動的な柔軟性ゆえに「動いてしまうコード」を簡単に書くことができる。しかし、アプリケーションが大規模化し、チームの人数が増え、TypeScriptによる型静的解析が当たり前になった現代においても、ランタイム(実行時)の境界線では依然としてこのようなプリミティブな型挙動の罠が牙を剥く。

「たかが `NaN` の判定」と侮るなかれ。
グローバルな `isNaN()` を使い続けることは、アプリケーションの境界線に穴を開け、予期せぬ型変換という名のトロイの木馬を招き入れることに等しい。

明日から、いや今すぐ、プロジェクト内の `isNaN` を検索し、すべて `Number.isNaN()` へリプレースしてほしい。コードの信頼性は、こうした細部への徹底的なこだわりからしか生まれないのだから。

コメント

タイトルとURLをコピーしました