もっと賢いデフォルト値の指定
JavaScriptには昔から value || fallback というデフォルト値の書き方があります。たいていはうまく動くのですが、実はちょっとしたクセがあります。「falsyな値」をすべて「値がない」とみなしてしまうのです。つまり 0、''、false のように、それ自体が正しい答えであってもフォールバック側が発動してしまいます。
そこで登場するのが Null合体演算子 ?? です。?? 演算子はフォールバックを返すのが null か undefined のときだけ。これで先ほどの問題が解決します。
0 も '' もそのまま残る。消えるのは null と undefined だけ。Null合体演算子はこれが全てです。
|| だけでは足りない理由
?? 演算子がそもそもなぜ生まれたのか。その背景には、|| による“よくあるバグ”があります。
ユーザーはボリューム 0(無音)と空のニックネームを指定しているのに、|| では「未指定」と「falsyな値」を区別できないため、どちらも勝手に上書きされてしまいます。
ここで ?? の出番です。
これで 0 や '' はそのまま通り抜け、本当に値が入っていないフィールドにだけデフォルトが当たるようになりました。実務ではほぼこの挙動が欲しいはずです。
イメージで掴む ?? 演算子
?? が聞いているのは1つだけ。左辺は null または undefined か? それ以外は対象外です。
| 左辺の値 | left || right の結果 | left ?? right の結果 |
|---|---|---|
null | 右辺 | 右辺 |
undefined | 右辺 | 右辺 |
0 | 右辺 | 左辺 (0) |
'' | 右辺 | 左辺 ('') |
false | 右辺 | 左辺 (false) |
NaN | 右辺 | 左辺 (NaN) |
| 任意のオブジェクト | 左辺 | 左辺 |
データに意味のある falsy 値(空文字やゼロなど)が含まれるなら ?? を選びましょう。逆に、falsy な値をまとめて弾きたい(短い文字列や件数ゼロなども一律に無効扱いしたい)場合は || の出番です。そういうケースも無くはないですが、みんなが思っているほど多くはありません。
短絡評価の挙動
|| や && と同じで、?? も短絡評価されます。左辺が nullish でなければ、右辺はそもそも評価されません。
expensiveDefault() が実行されるのは b のときだけ、1回きりです。フォールバックが関数呼び出しやネットワークリクエストなど、必要ないときは走らせたくない処理のときに重宝します。
オプショナルチェーンとの組み合わせ
?? と ?. はどちらも ES2020 で追加された機能で、セットで使うことを前提に設計されています。オプショナルチェーンは途中で値が無ければ undefined を返しながらプロパティをたどり、そこに Null合体演算子を重ねれば、いい感じのデフォルト値を差し込めます。
この組み合わせがないと、同じコードは && のチェックを延々と連ねたり、try/catch で囲んだりと、かなり泥臭くなります。これらを使えば、安全なアクセスとまっとうなデフォルト値の設定が1行で書けてしまいます。
Null合体代入 ??=
a ??= b は、a が null または undefined のときだけ b を代入します。いわゆる論理Null合体代入演算子で、ショートサーキット評価が効きます。つまり、a にすでに値が入っていれば、右辺は評価すらされません。
verbose: false と retries: 0 がそのまま残っている点に注目してください。??= が埋めるのは、本当に値が無いプロパティだけです。||= なら両方とも上書きされてしまうので、挙動の違いがはっきりわかります。
演算子の優先順位とカッコのルール
?? は、カッコなしで || や && と混ぜることを意図的に禁止しています。次のコードは SyntaxError になります。
const x = a || b ?? c; // SyntaxError
言語の設計者としては、優先順位が紛らわしいので明示的に書かせたほうがいい、という判断をしたわけです。括弧を付けてあげればちゃんと動きます。
グループ化を変えれば結果も変わります。この括弧は飾りではなく、「こう書いたつもりですよ」という意図をパーサーにも未来の読み手にも伝える大事な印です。
デフォルト引数とは別物
関数のデフォルト引数には独自のルールがあって、引数が undefined のときだけ発動し、null では働きません。null と undefined を同じように扱う ?? とは、ここが微妙に違うポイントです。
null でもフォールバックを効かせたい場合は、関数本体で ?? を使います。
APIが「値が未設定」を表すのにnullを返してくる場面では、ここの違いでつまずきがちです。
??を使うべき場面
基本的には??をデフォルトの演算子として使い、特別な理由があるときだけ別の方法を選ぶ、というスタンスがおすすめです。実際のデータの振る舞いに一番しっくりくるのが??だからです。0や''、falseは多くの場合「ちゃんとした値」なので残すべきで、本当にデータが欠けているときだけフォールバックを効かせたい——そんな場面にぴったりです。||の出番は、falsyな値ならなんでも置き換えたい、という明確な意図があるときだけに絞りましょう。
次回: クラス
オブジェクトと配列はこれでひと区切りです。データの形を安全に組み立てたり辿ったりする道具は揃いました。次の章からは振る舞いの整理に話が移ります。クラス、継承、そしてその裏側で動いているプロトタイプの仕組みを見ていきましょう。
よくある質問
Null合体演算子(??)とは何ですか?
??は左辺がnullまたはundefinedのときだけ右辺を返す演算子です。それ以外の場合は左辺をそのまま返します。value ?? fallbackという書き方で、0や空文字といった「falsyだけど有効な値」を誤って置き換えずにデフォルト値を設定できます。
??と||の違いは何ですか?
||は0・''・false・NaN・null・undefinedといったすべてのfalsy値でフォールバックします。一方??が反応するのはnullとundefinedだけ。0や空文字を正しい値として扱いたいなら??を使いましょう。逆に、falsyな値をすべて弾きたい場面では||のままで問題ありません。
オプショナルチェーン(?.)と組み合わせて使えますか?
はい、むしろ定番の組み合わせです。user?.settings?.theme ?? 'light'と書けば、途中のプロパティが存在しなくても安全にたどれて、結果がundefinedなら'light'に置き換わります。ES2020でこの2つが同時に導入されたのは、まさにこのパターンを想定してのことです。
??=演算子は何をしますか?
a ??= bは、aがnullまたはundefinedのときだけbを代入する演算子(論理null合体代入)です。a = a ?? bの短縮形ですが、ショートサーキット評価される点が違います。つまりaにすでに値が入っていれば右辺は評価されません。オプションオブジェクトの未設定項目にデフォルトを補うときに便利です。