現場のエンジニアを深夜に悩ませる「`??` と `||`」の深淵
フロントエンドのアーキテクチャを設計していると、データバインディングや状態管理のレイヤーで、幾度となく「デフォルト値のフォールバック」という課題に直面する。
APIから返ってきたレスポンス、あるいはローカルストレージから復元した設定値。そこに値が存在しない場合、あるいは「空」である場合に、安全な代替値を差し込む。この極めてプリミティブな処理において、JavaScriptエンジニアの技量が最も試されるのが、論理和演算子(`||`)とNull合体演算子(`??`)の使い分けだ。
「とりあえず `||` を使っておけば動くやろ」――もし君のコードベースでそんな安易な実装が蔓延しているなら、今すぐ立ち止まってほしい。その油断が、ユーザーの入力した「0」を握りつぶし、空文字のプロフィールをデフォルト値で上書きし、プロダクション環境で静かに、しかし確実にバグを発生させている。
今回は、V8などのJavaScriptエンジンがどのようにこれらの演算子を評価しているのか、その内部挙動とメモリ、そして堅牢なWebアプリケーション構築のためのアーキテクチャ的視点から、このテーマを骨の髄まで解剖していく。
—
Falsyの呪縛:`||` が抱える致命的な設計上のジレンマ
まず、JavaScriptにおける「真偽値評価(Truthy / Falsy)」の歴史的背景を振り返ろう。
`||`(論理和)演算子は、本来は真偽値を評価するためのものだ。しかし、JavaScriptの動的型付けの歴史的経緯から、左辺が「Falsyな値」である場合に右辺を返すという、いわゆる「フォールバック用途」として長年酷使されてきた。
ここで思い出してほしい。JavaScriptにおけるFalsyな値とは何か?
- `false`
- `0`(数値のゼロ)
- `-0`(負のゼロ)
- `0n`(BigIntのゼロ)
- `””`(空文字列)
- `null`
- `undefined`
- `NaN`
そう、`0` も `””`(空文字)も、厳密には「有効なデータ」であるにもかかわらず、JavaScriptのエンジン内部では「Falsy」としてひとまとめに判定されてしまうのだ。
// ユーザーがフォームで「0回」と入力した、あるいは年齢として「0」を設定したとする
const userScore = 0;
// 悪名高い || によるフォールバック
const displayScore = userScore || 10;
console.log(displayScore); // 10 (ユーザーが意図した「0」が消滅する!)
この挙動は、単なる「仕様の勘違い」では済まされない。例えば、ECサイトの数量選択で「0個」が購入できなくなったり、UIのグリッドシステムで「column: 0」というレイアウト指定がデフォルト値に書き換わって予期せぬレンダリング負荷を引き起こしたりと、実務において致命的なバグの温床となる。
—
Null合体演算子(`??`)の登場:厳密な「無効値」の定義
この長年のフラストレーションを解消するために、ES2020で導入されたのが Null合体演算子(Nullish Coalescing Operator: `??`) だ。
`??` の判定基準は極めて明快かつ厳格である。左辺が `null` または `undefined` である場合のみ、右辺を返す。それ以外の値(`0`、`””`、`false`、`NaN`など)は、たとえそれがFalsyであっても「有効な値」としてそのままスルーして通過させる。
const userScore = 0;
const userName = “”;
const isActive = false;
// 厳密な Nullish 判定
console.log(userScore ?? 10); // 0 (保持される)
console.log(userName ?? “ゲスト”); // “” (保持される)
console.log(isActive ?? true); // false (保持される)
// 当然、本当にデータが存在しない場合はフォールバックが機能する
const apiResponse = { timeout: null };
console.log(apiResponse.timeout ?? 3000); // 3000
この挙動の違いは、ブラウザのJITコンパイラ(V8のTurboFanなど)による最適化の文脈においても非常にクリーンだ。無駄な型変換のオーバーヘッドを発生させず、単に「ポインタが指し示す先が `null` / `undefined` かどうか」を低水準でチェックするだけで済むため、極限までパフォーマンスが最適化されている。
—
アーキテクチャ視点での使い分け:境界線の引き方
では、実際のモダンWebアプリケーション開発において、どのようにこの2つを使い分けるべきか。シニアエンジニアとして持っておくべき「判断基準のアーキテクチャ」を提示しよう。
1. プリミティブな数値・真偽値・文字列を扱う場合
APIレスポンスのパースや、ユーザー入力のバリデーション結果、コンポーネントのPropsのデフォルト値などにおいて、`0` や `false`、`””` が「意味を持つデータ」である場合は、必ず `??` を使用する。
// TypeScriptを用いた堅牢なPropsのフォールバック例
interface ButtonProps {
// 0ミリ秒のアニメーション遅延も「有効な値」として扱いたい
delay?: number;
label?: string;
}
function resolveButtonConfig(props: ButtonProps) {
return {
// ❌ NG: delayが 0 の時にデフォルトの 300ms に上書きされてしまう
// delay: props.delay || 300,
// ⭕️ OK: undefined の時のみ 300ms にフォールバックする
delay: props.delay ?? 300,
label: props.label ?? “確認”,
};
}
2. 「存在しないこと」と「空であること」を同義として扱う場合
一方で、文字列のフォールバックにおいて、`null` や `undefined` だけでなく、「空文字列(`””`)や空白文字のみの入力も、未入力として扱いたい」というビジネスロジックが存在する。この場合は、依然として `||` や、より明示的な条件分岐がその役割を担う。
// ユーザーの入力値が「未入力(null/undefined)」または「空文字」の場合にデフォルトを表示したい
const userInput = “”; // または null
// 空文字も未入力とみなす場合のイディオム
const displayName = userInput || “名無しさん”;
console.log(displayName); // “名無しさん”
※ただし、この場合でも `userInput` が `0` や `false` になり得るコンテキストではバグを生むため、実務では `userInput ? userInput : “名無しさん”` や `userInput !== “” && userInput != null ? userInput : …` のように、型安全性を担保したヘルパー関数を挟むのがプロの仕事だ。
—
注意すべきピットホール:`??` と `||` の混在禁止
ECMAScriptの仕様において、`??` と `||`(または `&&`)を括弧なしで直接混在させることは構文エラー(SyntaxError)として厳格に禁止されている。これは、開発者に「優先順位の曖昧さによるバグ」を意図的に起こさせないための、言語仕様レベルのセーフティネットだ。
// ❌ 構文エラーになる(どちらを先に評価すべきかエンジンが判断できないため)
const value = a ?? b || c;
// ⭕️ 意図を明確にするために必ず括弧で明示する
const safeValue = (a ?? b) || c;
このエラーに直面したときは、自身のコードの設計が曖昧になっていないかを見直す絶好の機会だ。論理演算の優先順位に頼るのではなく、意図した評価順序を括弧で明確にスコープ化する、あるいは一時変数や専用のユーティリティ関数に切り出すことで、コードの保守性と可読性は劇的に向上する。
—
まとめ:型判定の本質を見極める
JavaScriptにおける `??` と `||` の違いは、単なる「書き方の好み」ではない。それは、「開発者がデータの意味(Semantics)をどこまで厳密に定義しているか」の表れだ。
- `||` は、真偽値の文脈、あるいは「Falsyな値すべてをデフォルト値に置き換えたい」というレガシーかつ限定的なユースケースにのみ使う。
- `??` は、現代の型安全なWebアプリケーションにおいて、値の「不在(Nullish)」を正確に検知し、堅牢なフォールバックを実現するためのスタンダードとして積極的に採用する。
細部へのこだわりが、メモリ効率の最適化、レンダリング負荷の軽減、そして何より「深夜の緊急障害対応」をゼロにする美しいコードベースを創り上げる。
さあ、今すぐ君のプロジェクトのコードベースから、不適切な `||` を探し出し、モダンで堅牢な `??` へとリファクタリングしよう。

コメント