深いところを辿ると途中で落ちます。オプショナルチェーンで落ちないようにする方法、分割代入で必要な値だけ取り出す書き方、既定値の付け方と ?? と || の違いを確かめます。
実際のデータは入れ子になっています。深く辿るのは、そのままだと危険です。
example.js
const data = { user: { profile: { name: 'あ' } } };
console.log(data.user.profile.name);
出力書き換えて実行できます
あ
javascript
const data = { user: { profile: { name: 'あ' } } };
console.log(data.user.address.city);
TypeError: Cannot read properties of undefined (reading 'city')
途中の鍵が無いと落ちます。
example.js
const data = { user: { profile: { name: 'あ' } } };
console.log(data.user?.address?.city);
console.log(data.user?.profile?.name);
出力書き換えて実行できます
undefined
あ
example.js
const data = { count: 0, label: '' };
console.log(data.count?.toFixed(1));
console.log(data.label?.length);
出力書き換えて実行できます
0.0
0
data.user.address が undefined で、その city を読もうとしたからです。
落ちるのは最後の1段で、原因はその手前にあります。
→ Cannot read properties of undefined
?. で落ちないようにする
?. は、左が null か undefined なら、そこで止めて undefined を返します。
example.js
const api = { load: () => 'あ' };
console.log(api.load?.());
console.log(api.save?.());
const list = null;
console.log(list?.[0]);
出力書き換えて実行できます
あ
undefined
undefined
javascript
console.log(data?.user?.profile?.name);
これをオプショナルチェーンと呼びます。
止まるのは null と undefined のときだけです。
0 や空文字では止まりません。
example.js
const data = { user: {} };
console.log(data.user?.address?.city ?? '未設定');
出力書き換えて実行できます
未設定
example.js
const settings = { count: 0, label: '', flag: false };
console.log(settings.count ?? 10, settings.count || 10);
console.log(JSON.stringify(settings.label ?? 'なし'), JSON.stringify(settings.label || 'なし'));
console.log(settings.flag ?? true, settings.flag || true);
出力書き換えて実行できます
0 10
"" "なし"
false true
関数と配列にも使える
example.js
const user = { name: 'あ', age: 20, address: { city: '東京' } };
const { name, age } = user;
console.log(name, age);
出力書き換えて実行できます
あ 20
example.js
const user = { name: 'あ', address: { city: '東京' } };
const { address: { city } } = user;
console.log(city);
出力書き換えて実行できます
東京
?.() は「関数があれば呼ぶ」、?.[] は「あれば取り出す」です。
無い関数を呼んでも落ちません。
使いすぎない
example.js
const user = { name: 'あ' };
const { name: userName, nothing = '既定値' } = user;
console.log(userName);
console.log(nothing);
出力書き換えて実行できます
あ
既定値
全部に付けるのは、考えるのをやめている合図です。
data が必ずあると分かっているなら、そこに ?. は要りません。
本当は「無いはずがない」ところが undefined になっていても、?. が黙って隠します。
付けるのは「無いことがありうる」と分かっている場所だけにしてください。
既定値を付ける
?? は、左が null か undefined のときだけ右を返します。
example.js
const user = { a: null, b: undefined };
const { a = '既定値', b = '既定値' } = user;
console.log(a);
console.log(b);
出力書き換えて実行できます
null
既定値
example.js
function show({ name, city = '不明' }) {
return `${name}(${city})`;
}
console.log(show({ name: 'あ', city: '東京' }));
console.log(show({ name: 'い' }));
出力書き換えて実行できます
あ(東京)
い(不明)
|| との違い
ここは必ず踏む落とし穴です。
|| は0・空文字・false も「無い」と見なします。
- 件数が
0 なのに 10 になる
- 入力が空なのに「なし」に置き換わる
- 設定を
false にしたのに true になる
「値が無いとき」の既定値は ??、「偽のとき」の代替は ||。
迷ったら ?? です。
分割代入で取り出す
必要な値だけを、名前を付けて取り出せます。
入れ子も取り出せます。
ただし address が無いと落ちます。 深い分割代入は、
あることが確実なときだけにしてください。
名前を変える・既定値を付ける
name: userName … 別の名前で受け取る。他の変数と衝突するときに使う
nothing = '既定値' … 無いときだけ使われる
既定値が働くのはundefined のときだけです。null では働きません。
?? は null でも働き、分割代入の既定値は働きません。 ここが違います。
関数の引数で使う
分割代入がいちばん効くのは、関数の受け口です。
何を使うのかが、呼び出し側から見えます。
引数の順番を覚えなくてよくなるのも利点です。
まとめ
- 途中の鍵が無いと落ちる。原因は落ちた場所の手前
?. はnull と undefined のときだけ止まる。0 や空文字では止まらない
- 全部に
?. を付けない。 隠すべきでない間違いまで隠れる
- 既定値は
??。|| は 0・空文字・false も置き換える
- 分割代入の既定値は
undefined のときだけ働く(null では働かない)
- 関数の引数で使うと、何を使うのかが呼び出し側から見える
次に学ぶ
オブジェクトの中身を全部まとめて扱う方法を学びます。
Object.keys() / values() / entries() で回し、
配列のメソッドと組み合わせて変換する形までやります。
→ 第3回: オブジェクトを回す・変換する