?. と ?? — 無いかもしれない値を安全に読む

?. は null と undefined のときだけ止まります。?? は 0 や空文字を既定値で潰しません。連鎖が丸ごと止まること、?. が守ってくれない場面まで実行して確かめます。

実行して検証済み処理系Node 22.22.3対応ES2020難易度●●○○○目安9更新

?. は、左が nullundefined なら、そこで止まって undefined を返します。

example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
東京 undefined TypeError: Cannot read properties of undefined (reading 'city')
example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
1 undefined 20 undefined よんだ undefined

同じ場所で、落ちるundefined になるかが変わります。

書ける場所は3つ

example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
null -> undefined undefined -> undefined 0 -> Number "" -> String false -> Boolean
example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
undefined 引数を評価した回数: 0
書き方 何を守るか
a?.b 鍵で読む
a?.[i] 添字や変数で読む
a?.() 呼ぶ

a?.ba[b] を混ぜるときは a?.[b] です。 a?[b] とは書けません。

止まるのは2つの値のときだけ

example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
undefined
javascript
const source = {};

console.log((source.a?.b).c);
TypeError: Cannot read properties of undefined (reading 'c')

0 も空文字も false も、止まりません。 「値が無い」と「値が偽になる」は別のことです。

止まると、その先も評価されない

example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
null | "既定" | "既定" undefined | "既定" | "既定" 0 | 0 | "既定" "" | "" | "既定" false | false | "既定" "あ" | "あ" | "あ"
example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
0 3 1000

引数すら評価されません。 これを短絡評価と呼びます。

連鎖は丸ごと止まる

ここは誤解されがちです。

example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
{"a":0,"b":9,"c":9}
javascript
const options = { a: 0, b: null };

options.a ||= 9;
options.b ||= 9;

console.log(JSON.stringify(options));
{"a":9,"b":9}

?. の後ろに ?. が無くても落ちません。 a が無かった時点で、その連鎖の残り全部が飛ばされます。

ただし、括弧で切ると別の式になります。

javascript
const x = a || b ?? c;
javascript
const x = (a || b) ?? c;

括弧で囲むと、そこで連鎖が終わります。 意味もなく囲まないでください。

?? — 既定値を入れる

?? は、左が null か undefined のときだけ右を返します。

example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
TypeError: notFunction.f is not a function
example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
ReferenceError: notDeclared is not defined undefined

||0 と空文字と false も潰します。 これが古くからある事故のもとです。

javascript
function total(order) {
return order?.items?.reduce?.((sum, item) => sum + item?.price, 0);
}

console.log(total({ items: [{ price: 1 }, { price: 2 }] }));
console.log(total(null));
console.log(total({}));
3 undefined undefined
example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
3 0

「再試行しない」という設定の 0 が、|| だと3回に化けます設定値の既定は ?? で書いてください。

「空文字も既定に倒したい」ときは || が正しい選択です。値として 0 や空文字が意味を持つかどうかで決めます。

??= — 無いときだけ入れる

||= にすると 0 まで書き換わります。

関数の引数 — 既定値・可変長・分割代入

??&& || と並べられない

これは構文として読めません。 括弧を書けば通ります。

優先順位が直感と食い違うため、言語のほうで括弧を強制しています。構文の誤りなので、このページでは実行例にできません。

?. が守ってくれないもの

関数でない値を呼ぶ

?.() が見ているのはあるか無いかだけです。 関数かどうかは見ていません。TypeError: x is not a function

宣言されていない名前

?.値を見るもので、名前を探すものではありません。 名前の存在を調べるなら typeof です。 → ReferenceError: x is not defined

よくある間違い:とりあえず全部に付ける

落ちなくはなりました。ですが、items を渡し忘れても undefined が静かに返ります。 その undefined は、呼んだ側のもっと遠い場所で落ちます。

?.欠けていて当たり前の場所にだけ付けてください。 あるはずのものが無いなら、そこで落としたほうが直しやすいです。

入れ子のオブジェクトを安全にたどる

まとめ

  • ?. が止まるのはnull と undefined のときだけ0 や空文字では止まらない
  • 書ける形は a?.b / a?.[i] / a?.() の3つ
  • 止まると残りの連鎖ごと飛ばされる。括弧で囲むとそこで切れる
  • ??|| と違い、0 と空文字と false を潰さない
  • ??&& || と並べるには括弧が要る
  • ?.()関数かどうかを見ていない。未宣言の名前も守れない
  • 欠けていて当たり前の場所にだけ付ける

この記事の根拠

  1. Optional Chains — ECMAScript® 2026 Language Specificationtc39.es
  2. Binary Logical Operators — ECMAScript® 2026 Language Specificationtc39.es
  3. オプショナルチェーン (?.) — MDNdeveloper.mozilla.org

掲載しているコード例は、公開前に読み手が押したときに動くのと同じもので実行して出力を突き合わせています。結果はリポジトリに残しています。

このページの原文(Markdown)