こんにちは。フロントエンドの現場で日々コードと向き合っていると、たまに「なんでこの条件分岐が `true` になるんだ……?」と頭を抱えたくなる瞬間に遭遇しますよね。
特に、テストコードを書いている時や、レガシーなAPIレスポンスのバリデーションを直している時。魔女の悪戯のように潜む `==`(抽象等価演算子)の挙動に足をすくわれた経験がある人は、僕だけではないはずです。
「とりあえず動くから `==` でいいや」なんてコードを書いていたら、深夜の障害対応で泣きを見るのは自分(あるいは後輩)です。今回は、JavaScriptのエンジンが裏側でどうやってあの厄介な `==` を処理しているのか、その泥臭いアルゴリズムの正体を暴き、実務で二度とバグを生み出さないための防衛策を徹底的に解説していきましょう。
—
そもそも、なぜ `==` はバグの温床になるのか?
JavaScriptは「動的型付け言語」として生まれて以来、開発者の記述量を減らすために、異なる型同士の比較であっても「よしなに気を利かせて」比較しようとする優しさ(あるいは過剰なお節介)を持っています。
そのお節介の象徴が、抽象等価演算子 `==` です。
例えば、以下のコードを見てみてください。
console.log(0 == ”); // true
console.log(false == ‘0’); // true
console.log(null == undefined); // true
直感的にはすべて `false` になりそうですが、JavaScriptのエンジンはこれらをすべて「同じ」と判定します。
「いやいや、型が違うんだから普通は別物でしょ!」と突みたくなりますが、これにはECMAScript仕様書で厳密に定義された「抽象等価比較アルゴリズム(Abstract Equality Comparison Algorithm)」という明確なルールが存在します。
ブラウザの裏側で何が起きているのか、そのフローチャートの核心を覗いてみましょう。
—
裏側で何が起きている? `==` の比較アルゴリズムを完全分解
JavaScriptエンジンが `A == B` に遭遇したとき、脳内で実行している手順はざっくり言うと以下のステップです。
1. 型のチェック: `A` と `B` が同じ型なら、そのまま厳密等価 (`===`) の比較をする。
2. `null` と `undefined` の特別ルール: `null` と `undefined` 同士の比較は無条件で `true`。
3. 数値と文字列の衝突: どちらかが数値で、もう一方が文字列なら、文字列を数値に強制変換して再比較。
4. ブール値の存在: どちらかが真偽値 (`boolean`) なら、それを数値に変換 (`true` -> `1`, `false` -> `0`) して再比較。
5. オブジェクトとプリミティブ: どちらかがオブジェクトで、もう一方がプリミティブ(文字列や数値)なら、オブジェクトを `valueOf()` や `toString()` を使ってプリミティブに強制変換してから再比較。
文字で読んでもピンとこないと思うので、実務で特にハマりやすいパターンをいくつか例に取って、エンジンが裏側でどんなトランスフォームを行っているのか追ってみましょう。
パターン1: `0 == ”` (数値と空文字列)
- ステップ適用: 一方が数値 (`0`)、もう一方が文字列 (`”`)。
- 変換: ルールに従い、空文字列 `”` が数値に変換されます。`Number(”)` の結果はなんと `0` です。
- 結果: `0 == 0` となり、結果は `true`。
パターン2: `false == ‘0’` (真偽値と文字列)
- ステップ適用: 真偽値が含まれています。
- 変換の連鎖:
1. まず、真偽値の `false` が数値に変換され、`0` になります (`0 == ‘0’`)。
2. 次に、文字列の `’0’` が数値に変換され、`0` になります (`0 == 0`)。
- 結果: 最終的に `0 == 0` となり、結果は `true`。
……どうですか? なかなかのカオスティクスですよね。人間が脳内でここまで型変換のステップを追うのは時間の無駄ですし、認知負荷が高すぎます。
—
現場で即実践できる「比較の鉄則」とベストプラクティス
じゃあ、実務ではどう立ち回るべきなのか。結論から言えば、「原則として `==`(および `!=`)は一切使わない」、これに尽きます。
チーム開発におけるコードレビューで、もし `==` を見つけたら、それはリファクタリングの絶好のチャンスです。以下のベストプラクティスをチームの共通認識として共有してください。
1. 厳密等価演算子 (`===` / `!==`) を強制する
型変換(Type Coercion)を自動で行わせるのではなく、型も値も完全に一致しているかを検証する `===` を使いましょう。これだけで、上記のような暗黙の型変換に起因するバグの99%は消し去ることができます。
// 【NG】暗黙の型変換が起きるため意図しない挙動の温床になる
if (userId == “123”) {
// …
}
// 【OK】型と値の両方を厳密にチェックする
if (userId === 123) {
// …
}
2. 「例外的に許容されるケース」:唯一の例外は `null` と `undefined` の判定
厳格なJSプログラマの間でも、唯一 `==`(あるいは `!=`)の使用が許容される(というか、実用的な)テクニックがあります。それが、「変数が `null` または `undefined` のいずれかであるか」を一度にチェックしたい場合です。
// フォームの入力値やAPIのレスポンスが「未定義(値が存在しない)」であることを判定したい
const value = response.data;
// 【スマートな書き方】
// null と undefined はお互いに == で true になる仕様を利用する
if (value == null) {
console.log(“値は null または undefined です”);
}
// 【冗長な書き方(間違いではないが長い)】
if (value === null || value === undefined) {
console.log(“値は null または undefined です”);
}
※ただし、このテクニックを使う場合も、チーム内で「ここは意図的に `== null` を使っている」という共通の文脈(コメントやコーディング規約)がないと、後から見たジュニアエンジニアが「おっ、==使ってるじゃん、直しとこ」と勝手に `===` に書き換えてバグを埋め込むリスクがあるので注意してください。
3. ESLintで機械的に排除する
人間の注意力に頼るのではなく、機械に強制させるのがプロのエンジニアリングです。
ESLintを使っているなら、`eqeqeq` ルールを `error` に設定し、`==` の使用をビルド時に完全にブロックしましょう。
`.eslintrc.js` の設定例:
module.exports = {
rules: {
// 常に === と !== の使用を強制し、== をエラーにする
// 例外的に null との比較を許可したい場合はオプションを追加することもあるが、原則 error 推奨
“eqeqeq”: [“error”, “always”]
}
};
—
まとめ
JavaScriptの抽象等価演算子 `==` は、言語の歴史的な経緯が生んだ複雑なアルゴリズムの塊です。その裏側の仕組みを知ることは、JavaScriptという言語のコアな挙動を理解する上で非常に有益ですが、「知っているからといって、実務であえて使う必要はない」というのがシニアとしての結論です。
コードは「書く時」よりも「読まれる時」、そして「保守される時」に圧倒的なコストがかかります。
明日からのコーディングでは `===` を相棒にして、予期せぬ型変換の魔力から自身のコードベースをしっかりと守り抜いていきましょう!

コメント