やあ、調子はどうだい?
日々のコンポーネント実装や状態管理の設計で忙殺されていることと思うが、フロントエンドの足元を支えるJavaScriptの仕様、特に「型と値の比較」について深く考えたことはあるかい?
「動くからいいや」でコードを書いていると、ある日突然、浮動小数点数の計算ズレや`NaN`の気まぐれに足元をすくわれる。これが実務の現場で一番怖い瞬間だ。
今日は、中級からもう一段上のシニアへとステップアップしたい君に向けて、JavaScriptにおける値の比較の急所、`Object.is`と厳密等価演算子(`===`)の決定的な違いについて、ブラウザの裏側の動きまで含めて徹底的に解説しよう。
—
なぜ `===` だけでは戦えないのか?
JavaScriptのコードを書くとき、等価比較には大体 `===`(厳密等価演算子)を使うはずだ。「型も値も厳密に比較してくれるから安心」――そう思っていないかい?
実は、`===` にはJavaScriptの歴史的・仕様上の「2つの大きなバグ(あるいは妥協)」とも呼ぶべき挙動が存在する。それが以下のケースだ。
1. `NaN` 同士の比較が `false` になる
2. `+0` と `-0` が区別できず、同じ(`true`)と判定される
ECMAScript 6 (ES2015) で登場した `Object.is` は、まさにこの `===` の「痒いところに手が届かない仕様」を補正するために生まれた。まずは、百聞は一見に如かず、実際の挙動をコードで確認してみよう。
// === と Object.is の基本的な挙動の違いを比較するハンズオン
console.log(‘— 1. 通常の値の比較 —‘);
console.log(5 === 5); // true
console.log(Object.is(5, 5)); // true
console.log(‘— 2. NaN の比較 —‘);
console.log(NaN === NaN); // false !!!(これが初見殺し)
console.log(Object.is(NaN, NaN)); // true (正しく同一と判定)
console.log(‘— 3. +0 と -0 の比較 —‘);
console.log(+0 === -0); // true (区別してくれない)
console.log(Object.is(+0, -0)); // false (正しく別物と判定)
どうだろう? `NaN === NaN` が `false` になるのは有名だが、改めて見るとやっぱり狂っていると感じないか?この挙動の裏側で、ブラウザ(JavaScriptエンジン)は一体何をしているのだろうか。少し深掘りしてみよう。
—
ブラウザの裏側:なぜこんな挙動になるのか?
JavaScriptの数値は、IEEE 754という国際標準規格に基づく「倍精度浮動小数点数(64ビット)」としてメモリ上に保持されている。V8などのエンジンも、基本はこの仕様に忠実だ。
1. なぜ `NaN === NaN` が `false` なのか?
`NaN`(Not-a-Number)は、「数値ではないこと」を表す特別な値だ。例えば、計算結果が不定値になったり(`0 / 0`)、数値化できない文字列をパースしたとき(`Number(‘hello’)`)に返される。
IEEE 754の規格では、「NaNは、自分自身を含むいかなる浮動小数点数とも等しくない」と厳格に定義されている。これは設計ミスではなく仕様なのだ。
そのため、`===` はこのIEEE 754の比較アルゴリズムをそのまま忠実に実行してしまうため、`NaN === NaN` は `false` を返す。
2. なぜ `+0` と `-0` が存在し、`===` はそれらを同一視するのか?
コンピュータの世界において、数は「符号(プラス・マイナス)」と「絶対値」に分かれてメモリに格納される。そのため、ゼロにも「正のゼロ(`+0`)」と「負のゼロ(`-0`)」が存在し得る。
フロントエンドでアニメーションの速度計算や、極限値(Limit)を扱う数学的な処理を行う際、この「どちらの方向からゼロに近づいたか(`+0` or `-0`)」が極めて重要な意味を持つことがある。
しかし、通常のプログラミングにおいて、開発者がわざわざ `+0` と `-0` を区別して比較したいケースは稀だ。そのため、言語の使い勝手を考慮し、`===` はこれらを「同じ値」として扱うように設計されている。
—
実務でどう使い分けるべきか?(ベストプラクティス)
ここまで読んで、「じゃあ、明日から全部 `Object.is` に置き換えればいいのか?」と思ったかもしれない。
答えは「NO」だ。 実務におけるベストプラクティスを授けよう。
原則は `===` で十分
日々の開発、例えば「条件分岐」「状態管理のフラグ判定」「配列のフィルタリング」などでは、これまで通り `===` を使うべきだ。なぜなら、読み手にとって最も直感的であり、パフォーマンスの面でもエンジン側で最適化され尽くしているからだ。
`Object.is` を投入すべき「ピンポイントな状況」
`Object.is` を使うべきなのは、「データの整合性が命取りになる汎用的なユーティリティ関数」や「カスタムフック、状態管理ライブラリの比較関数(メモ化の判定など)」を自作しているときだ。
実務でそのままコピーして使える、堅牢な値比較ユーティリティのコードを見てほしい。
/
- 現場で使える堅牢な値の同一性判定ユーティリティ
- Reactの useMemo や useCallback の依存配列の比較、あるいは状態の変更検知に応用可能
/
function strictAreEqual(a, b) {
// 基本は Object.is で安全に比較(NaN や +0/-0 の問題をクリア)
if (Object.is(a, b)) {
return true;
}
// ここから先は、プリミティブを超えた「オブジェクトや配列の構造的比較(ディープエフェクト)」が必要な場合の拡張
// ※実務では lodash.isEqual などの信頼できるライブラリを使うのが定石
if (typeof a === ‘object’ && a !== null && typeof b === ‘object’ && b !== null) {
// 簡易的なプロパティ数のチェック
const keysA = Object.keys(a);
const keysB = Object.keys(b);
if (keysA.length !== keysB.length) return false;
// 再帰的に比較するロジックをここに挟む(省略)
}
return false;
}
// — 使用例 —
const stateA = { count: NaN };
const stateB = { count: NaN };
// 通常の === ではオブジェクトの参照が違うため false になるが、
// 内部のプリミティブ値(NaN)の比較に Object.is を活用する設計に組み込める。
特に、Reactの `React.memo` やカスタムフック内でのメモ化(`Object.is` はReactの内部でも `use-sore-external-store` や一部の比較処理で使われている)において、`NaN` を扱うグラフ描画ライブラリや金融系のフロントエンドを実装する際には、この `Object.is` の知識が君のコードをバグから救う盾になるはずだ。
—
まとめ
- `===` は、通常の開発における大半のケースで最適だが、`NaN` と `+0/-0` の判定において仕様上の特殊な挙動(バグではなく仕様)を持つ。
- `Object.is` は、その例外を綺麗にラップし、数学的な「値の同一性(SameValue)」を正確に保証してくれる。
- 実務では普段は `===` を使いつつ、「厳密な数値計算」「キャッシュの無効化判定(メモ化)」「特殊なデータの状態監視」など、正確性が求められる局面で `Object.is` を的確に選択しよう。
技術の裏側の仕様を知ることで、君のコードの信頼性は一段と引き締まる。明日からの実装に、ぜひこの知見を活かしてくれ。さて、次のタスクに取り掛かろうか!

コメント