---
type: reference
language: javascript
slug: operator/optional-chaining
title: "?. と ?? — 無いかもしれない値を安全に読む"
title_tag: "JavaScript の ?. と ?? — 使い方と || との違い"
summary: >
  ?. は null と undefined のときだけ止まります。?? は 0 や空文字を既定値で潰しません。
  連鎖が丸ごと止まること、?. が守ってくれない場面まで実行して確かめます。
description: >
  ?. が止まるのは null と undefined のときだけです。?? と || の違い、連鎖が丸ごと短絡すること、
  関数でない値や未宣言の名前は守られないことまで、実行しながら確かめられます。
status: published
difficulty: 2
minutes: 9

versions:
  verified: "Node 22.22.3"
  since: "ES2020"
  deprecated: null
  removed: null

sources:
  - title: "Optional Chains — ECMAScript® 2026 Language Specification"
    url: "https://tc39.es/ecma262/#sec-optional-chains"
  - title: "Binary Logical Operators — ECMAScript® 2026 Language Specification"
    url: "https://tc39.es/ecma262/#sec-binary-logical-operators"
  - title: "オプショナルチェーン (?.) — MDN"
    url: "https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Optional_chaining"

terms: [オプショナルチェーン, 空値合体, 短絡評価, 論理代入]

links:
  related:
    - javascript/errors/cannot-read-properties-of-undefined
    - javascript/learn/object/nested
    - javascript/reference/function/parameters

content_updated_at: 2026-09-08
published_at: 2026-09-08
---

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

```js run
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);
}
```
```output
東京
undefined
TypeError: Cannot read properties of undefined (reading 'city')
```

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

## 書ける場所は3つ

```js run
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?.());
```
```output
1 undefined
20 undefined
よんだ undefined
```

| 書き方 | 何を守るか |
|---|---|
| `a?.b` | 鍵で読む |
| `a?.[i]` | [num:添字]や変数で読む |
| `a?.()` | 呼ぶ |

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

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

```js run
for (const value of [null, undefined, 0, '', false]) {
  console.log(JSON.stringify(value), '->', String(value?.constructor?.name));
}
```
```output
null -> undefined
undefined -> undefined
0 -> Number
"" -> String
false -> Boolean
```

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

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

```js run
let called = 0;
const arg = () => {
  called++;
  return 1;
};

const nothing = null;

console.log(nothing?.method(arg()));
console.log('引数を評価した回数:', called);
```
```output
undefined
引数を評価した回数: 0
```

**引数すら評価されません。** これを[type:短絡評価]と呼びます。

## 連鎖は丸ごと止まる

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

```js run
const source = {};

console.log(source.a?.b.c.d);
```
```output
undefined
```

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

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

```js bad
const source = {};

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

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

## `??` — 既定値を入れる

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

```js run
const values = [null, undefined, 0, '', false, 'あ'];

for (const value of values) {
  console.log(JSON.stringify(value), '|', JSON.stringify(value ?? '既定'), '|', JSON.stringify(value || '既定'));
}
```
```output
null | "既定" | "既定"
undefined | "既定" | "既定"
0 | 0 | "既定"
"" | "" | "既定"
false | false | "既定"
"あ" | "あ" | "あ"
```

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

```js run
const settings = { retry: 0, timeout: null };

console.log(settings.retry ?? 3);
console.log(settings.retry || 3);
console.log(settings.timeout ?? 1000);
```
```output
0
3
1000
```

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

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

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

```js run
const options = { a: 0, b: null };

options.a ??= 9;
options.b ??= 9;
options.c ??= 9;

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

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

```js bad
const options = { a: 0, b: null };

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

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

→ [関数の引数 — 既定値・可変長・分割代入](/ja/javascript/reference/function/parameters/)

## `??` は `&&` `||` と並べられない

```js
const x = a || b ?? c;
```

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

```js
const x = (a || b) ?? c;
```

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

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

### 関数でない値を呼ぶ

```js run
const notFunction = { f: 1 };

try {
  notFunction.f?.();
} catch (error) {
  console.log(error.name + ': ' + error.message);
}
```
```output
TypeError: notFunction.f is not a function
```

`?.()` が見ているのは[key:あるか無いか]だけです。
[bad:関数かどうかは見ていません。]
→ [TypeError: x is not a function](/ja/javascript/errors/is-not-a-function/)

### 宣言されていない名前

```js run
try {
  console.log(notDeclared?.a);
} catch (error) {
  console.log(error.name + ': ' + error.message);
}

console.log(typeof notDeclared);
```
```output
ReferenceError: notDeclared is not defined
undefined
```

`?.` は**値を見るもの**で、名前を探すものではありません。
名前の存在を調べるなら `typeof` です。
→ [ReferenceError: x is not defined](/ja/javascript/errors/is-not-defined/)

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

```js bad
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({}));
```
```output
3
undefined
undefined
```

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

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

```js run
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({}));
```
```output
3
0
```

→ [入れ子のオブジェクトを安全にたどる](/ja/javascript/learn/object/nested/)

## まとめ

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