?. は null と undefined のときだけ止まります。?? は 0 や空文字を既定値で潰しません。連鎖が丸ごと止まること、?. が守ってくれない場面まで実行して確かめます。
?. は、左が null か undefined なら、そこで止まって undefined を返します。
example.js
const user = { name: 'あ', address: { city: '東京' } };
const empty = {};
console.log(user.address?.city);
console.log(empty.address?.city);
try {
console.log(empty.address.city);
} catch (error) {
console.log(error.name + ': ' + error.message);
}
出力書き換えて実行できます
東京
undefined
TypeError: Cannot read properties of undefined (reading 'city')
example.js
const source = { a: { b: 1 }, list: [10, 20], fn: () => 'よんだ' };
const nothing = null;
console.log(source?.a?.b, nothing?.a?.b);
console.log(source.list?.[1], nothing?.[0]);
console.log(source.fn?.(), source.missing?.());
出力書き換えて実行できます
1 undefined
20 undefined
よんだ undefined
同じ場所で、落ちるかundefined になるかが変わります。
書ける場所は3つ
example.js
for (const value of [null, undefined, 0, '', false]) {
console.log(JSON.stringify(value), '->', String(value?.constructor?.name));
}
出力書き換えて実行できます
null -> undefined
undefined -> undefined
0 -> Number
"" -> String
false -> Boolean
example.js
let called = 0;
const arg = () => {
called++;
return 1;
};
const nothing = null;
console.log(nothing?.method(arg()));
console.log('引数を評価した回数:', called);
出力書き換えて実行できます
undefined
引数を評価した回数: 0
| 書き方 |
何を守るか |
a?.b |
鍵で読む |
a?.[i] |
添字や変数で読む |
a?.() |
呼ぶ |
a?.b と a[b] を混ぜるときは a?.[b] です。 a?[b] とは書けません。
止まるのは2つの値のときだけ
example.js
const source = {};
console.log(source.a?.b.c.d);
出力書き換えて実行できます
undefined
javascript
const source = {};
console.log((source.a?.b).c);
TypeError: Cannot read properties of undefined (reading 'c')
0 も空文字も false も、止まりません。
「値が無い」と「値が偽になる」は別のことです。
止まると、その先も評価されない
example.js
const values = [null, undefined, 0, '', false, 'あ'];
for (const value of values) {
console.log(JSON.stringify(value), '|', JSON.stringify(value ?? '既定'), '|', JSON.stringify(value || '既定'));
}
出力書き換えて実行できます
null | "既定" | "既定"
undefined | "既定" | "既定"
0 | 0 | "既定"
"" | "" | "既定"
false | false | "既定"
"あ" | "あ" | "あ"
example.js
const settings = { retry: 0, timeout: null };
console.log(settings.retry ?? 3);
console.log(settings.retry || 3);
console.log(settings.timeout ?? 1000);
出力書き換えて実行できます
0
3
1000
引数すら評価されません。 これを短絡評価と呼びます。
連鎖は丸ごと止まる
ここは誤解されがちです。
example.js
const options = { a: 0, b: null };
options.a ??= 9;
options.b ??= 9;
options.c ??= 9;
console.log(JSON.stringify(options));
出力書き換えて実行できます
{"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 が無かった時点で、その連鎖の残り全部が飛ばされます。
ただし、括弧で切ると別の式になります。
括弧で囲むと、そこで連鎖が終わります。 意味もなく囲まないでください。
?? — 既定値を入れる
?? は、左が null か undefined のときだけ右を返します。
example.js
const notFunction = { f: 1 };
try {
notFunction.f?.();
} catch (error) {
console.log(error.name + ': ' + error.message);
}
出力書き換えて実行できます
TypeError: notFunction.f is not a function
example.js
try {
console.log(notDeclared?.a);
} catch (error) {
console.log(error.name + ': ' + error.message);
}
console.log(typeof notDeclared);
出力書き換えて実行できます
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
function total(order) {
const items = order.items ?? [];
return items.reduce((sum, item) => sum + item.price, 0);
}
console.log(total({ items: [{ price: 1 }, { price: 2 }] }));
console.log(total({}));
出力書き換えて実行できます
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 を潰さない
?? を && || と並べるには括弧が要る
?.() は関数かどうかを見ていない。未宣言の名前も守れない
- 欠けていて当たり前の場所にだけ付ける