3MIKAN
仮想通貨直コン

0やfalseが初期値に変わる理由|JavaScriptの||と??の使い分け

0を指定したのに3になる、falseにしたのに有効になる。||と??の違いを、空文字列・null・undefined・NaNも含めて確認。初期値の補完と入力チェックを分ける実行例を紹介します。

3MIKANのキャラクターが設置されたスイッチと空の取り付け枠を比べ、無効の設定と未設定を区別するイラスト

再試行を無効にしたくて回数に0を指定したのに、初期値の3へ戻ってしまう。そんなときは、値の読み取り方だけでなく、value || 3という補完処理を確認してみてください。

||0false、空文字列も右辺へ置き換えます。nullundefinedだけを補いたいなら??を使うと、指定済みの0falseを残せます。ただし、??は入力値が正しいかを調べる道具ではありません。

まずは手元で再現する

実行用サンプルを開く・保存するから、default-value-boundaries.mjsという名前で保存して実行してください。

node default-value-boundaries.mjs

確認環境はNode.js v24.14.0です。追加パッケージ、アカウント、ネットワーク通信、ファイルの書き込みは不要です。サンプルは正常例と反例を照合し、最後に「すべての実行例が期待値と一致しました。」と表示します。以下のJavaScriptの枠も、それぞれ別の.mjsファイルで実行できます。

||は「未設定」だけを調べているのではない

右辺を"default"にしたとき、結果は次のように分かれます。

左辺の値 値 || "default" 値 ?? "default"
0 "default" 0
false "default" false
"" "default" ""
null "default" "default"
undefined "default" "default"
NaN "default" NaN
"0" "0" "0"

||は左辺を真偽として判定し、偽なら右辺を選びます。表の0false、空文字列などがこの対象です。一方、??は左辺がnullundefinedのときだけ右辺を選びます。どちらも、選んだ値そのものを返すため、結果が必ず真偽値になるわけではありません。ECMAScriptの評価規則

たとえばfalse ?? truefalseです。「通知を無効にする」という明示的な設定を保持できます。逆に、空の表示名を代替表示へ変えたい処理なら、空文字列でも右辺を選ぶことが目的に合う場合があります。

初期値を入れてから、使える値か確認する

再試行回数について、「未指定とnullは3回、0以上の整数なら指定どおり、それ以外はエラー」と決めてみます。これはこの設定項目で採用する方針です。

function retryCount(value) {
  const count = value ?? 3;
  if (!Number.isInteger(count) || count < 0) {
    throw new TypeError('回数は0以上の整数にしてください');
  }
  return count;
}

console.log(retryCount(0));         // 0
console.log(retryCount(undefined)); // 3
console.log(retryCount(null));      // 3

try {
  retryCount('0');
} catch (error) {
  console.log(error.name); // TypeError
}

0は残りますが、文字列の"0"は受け付けません。サンプルでは、負の数、小数、NaNInfinity、空文字列、falseも拒否することを確認しています。

NaN ?? 3の結果はNaNのままです。??に置き換えるだけで不正な計算結果まで直るわけではありません。値を補う条件と、補った後に許可する型・範囲を分けると、どちらの処理を変えるべきかが明確になります。MDNのnull合体演算子の説明

nullに意味があるなら、まとめて補わない

APIや設定によっては、nullが「不明」や「明示的に空」を表すことがあります。その意味を残したいなら、??で初期値に変えるのは適切ではありません。

const preserveNull = (value) => value === undefined ? 3 : value;
console.log(preserveNull(null));      // null
console.log(preserveNull(undefined)); // 3

console.log(Object.hasOwn({ count: undefined }, 'count')); // true
console.log(Object.hasOwn({}, 'count'));                 // false

前半はundefinedだけを補う例です。後半は「キーは存在するが値がundefined」と「キー自体がない」を分ける例です。どちらも単にobj.count ?? 3とすると同じ3になります。区別が必要な処理では、値だけでなくキーの有無も確認します。

保存前後にこうした区別が失われる問題は、JSONへの保存と読み戻しで変わる値でも扱っています。読み取り後の補完では、保存時に失った情報までは戻せません。

右辺の処理は、必要な場合だけ動く

初期値を作る関数を右辺に置いた場合も、nullundefinedのときだけ呼ばれます。

let called = 0;
const fallback = () => { called += 1; return 3; };

console.log(0 ?? fallback());    // 0
console.log(called);            // 0
console.log(null ?? fallback()); // 3
console.log(called);             // 1

||&&??を括弧なしで混ぜると、構文エラーになります。たとえばnull || 3 ?? 4は書けません。(null || 3) ?? 4のように、どこを先に判定するかを明示します。これは見た目の推奨ではなく、言語の構文上の規則です。ECMAScriptの構文と評価規則

置き換える前に、残したい値を決める

0falseが有効な設定なら、その値を未設定扱いにしないことが出発点です。nullundefinedを同じ未設定とするなら??nullを残すなら明示的な条件分岐を使います。

その後で、数値の範囲や文字列の形式を確認します。||をすべて??に置き換えるのではなく、その項目にとって「未設定」と「不正な値」が何かを決めると、初期値が意図せず上書きされる問題を直せます。

確認した一次情報