- JavaScript
JavaScript三項演算子の書き方|ネスト・複数条件と??の使い分け
JavaScriptの三項演算子(条件演算子)とは、条件 ? 真のときの値 : 偽のときの値 の形で、条件によって2つの値のどちらかを選ぶ演算子です。 if文が「処理を分ける文」であるのに対し、三項演算子は「値を返す式」なので、変数への代入・returnの直後・テンプレートリテラルの中など、値を書ける場所ならどこにでも置けます。
この記事では、基本の書き方に加えて、if文・三項演算子・&&・||・??・?. を「同じ処理をどこまで短く書けるか」で比べた使い分け表、0や空文字で結果が変わる例、ネストした三項演算子の整形とオブジェクトのマップ・早期returnへの置き換え、+ や代入と混ぜたときに優先順位で想定と違う結果になる例を扱います。掲載したコードはすべて Node.js v22.22.2 で実行し、出力をそのまま載せています。
三項演算子の早見表
| やりたいこと | 書き方 | 補足 |
|---|---|---|
| 2つの値から選ぶ | const label = age >= 18 ? '大人' : '子ども'; |
基本形。? の後が真、: の後が偽 |
| returnで使う | return n < 0 ? -n : n; |
式なので return の直後に書ける |
| アロー関数で使う | const abs = v => v < 0 ? -v : v; |
波かっこなしで1行にできる |
| 文字列に埋め込む | `${qty}${qty === 1 ? ' item' : ' items'}` |
${} の中は式なのでそのまま書ける |
| 複数条件(かつ) | a > 0 && b > 0 ? 'OK' : 'NG' |
&& は ? : より先に評価される |
| 複数条件(または) | a === 1 || a === 2 ? 'OK' : 'NG' |
|| も ? : より先に評価される |
| 3つ以上に分ける | s >= 80 ? 'A' : s >= 60 ? 'B' : 'C' |
右から結び付く。1条件1行に整形する |
| null・undefinedのときだけ既定値 | value ?? '既定' |
value != null ? value : '既定' と同じ結果 |
| 偽の値すべてに既定値 | value || '既定' |
0・空文字・false・NaN も既定値になる |
| nullかもしれない値のプロパティ | user?.name |
user ? user.name : undefined に近い |
| 文字列連結の中で使う | '合計: ' + (n > 0 ? 'あり' : 'なし') |
かっこで囲む(+ が先に評価されるため) |
| 値と表示の対応が多い | LABELS[status] ?? '不明' |
三項演算子のネストよりオブジェクトのマップ |
| 真偽値そのものが欲しい | const isAdult = age >= 18; |
? true : false は省ける |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「JavaScript三項演算子の書き方|ネスト・複数条件と??の使い分け」
三項演算子の基本の書き方
構文と読み方
三項演算子は、JavaScriptで唯一「3つの値(オペランド)」を取る演算子です。MDN では Conditional (ternary) operator(条件演算子)という名前で載っています。
条件 ? 条件が真のときの式 : 条件が偽のときの式
// basic.mjs(Node.js v22.22.2 で実行)
const age = 20;
const label = age >= 18 ? '大人' : '子ども';
console.log(label);
実行結果:
大人
読むときは「age >= 18 か? そうなら '大人'、そうでなければ '子ども'」と、? を「か?」、: を「そうでなければ」と読むと頭に入りやすくなります。
if文との書き比べ
同じ処理を if文で書くと次のようになります。
// if文で書く場合
let label;
if (age >= 18) {
label = '大人';
} else {
label = '子ども';
}
// 三項演算子で書く場合
const label2 = age >= 18 ? '大人' : '子ども';
行数が減ることに加えて、const で宣言できることが三項演算子の大きな利点です。if文では先に let で宣言してから代入する形になりますが、三項演算子なら宣言と同時に値が決まるので、あとから再代入される心配がなくなります。
真・偽の判定は「truthy / falsy」
条件の部分は、true / false そのものでなくても構いません。JavaScriptは条件を真偽値に変換して判定します。偽として扱われる値(falsy)は次の6種類で、MDN の条件演算子のページにも同じ一覧が載っています。
| 偽として扱われる値(falsy) | 例 |
|---|---|
false |
比較の結果など |
0(-0、0n を含む) |
件数・在庫数・座標 |
''(空文字) |
未入力のテキスト |
null |
「値がない」ことを表す値 |
undefined |
未定義のプロパティ・引数 |
NaN |
数値に変換できなかった結果 |
これ以外はすべて真(truthy) です。空の配列 [] と空のオブジェクト {} も真になる点は、ほかの言語から来た方がつまずきやすいところです。この「0 や空文字が偽になる」性質が、後で説明する || と ?? の使い分けに直結します。
書ける場所・書けない場所
三項演算子は式なので、値を書ける場所にはすべて置けます。
// 1. 変数への代入
const fee = isMember ? 0 : 500;
// 2. return の直後
function abs(n) {
return n < 0 ? -n : n;
}
// 3. アロー関数の本体
const abs2 = v => v < 0 ? -v : v;
// 4. テンプレートリテラルの中
const qty = 1;
console.log(`${qty}${qty === 1 ? ' item' : ' items'}`); // 1 item
// 5. 関数の引数
button.classList.toggle('is-active', isOpen ? true : false); // → isOpen だけでよい
// 6. オブジェクトのプロパティ値
const style = { color: hasError ? 'red' : 'inherit' };
一方、? や : の後ろには「式」しか書けません。if や return のような「文」を書くと構文エラーになります。Node.js v22.22.2 で試した結果は次のとおりです。
const v = true ? if (1) {} : 0;
SyntaxError: Unexpected token 'if'
const z = true ? return 1 : 2;
SyntaxError: Unexpected token 'return'
また、: 以降は省略できません。「真のときだけ値が欲しい」場合に : を省くと、次のエラーになります。
const b = 1 ? 2;
SyntaxError: Unexpected token ';'
「偽のときは何もしない」を書きたい場合は、後述の && を使うか、素直に if文で書きます。
if文・三項演算子・&&・||・??・?. の使い分け表
「if文を短く書きたい」ときの選択肢は、三項演算子だけではありません。やりたいことによって、いちばん短く読みやすい書き方が変わります。
同じ処理をどう短く書けるか
| やりたいこと | if文で書くと | 短い書き方 | 選ぶ演算子 |
|---|---|---|---|
| 条件で2つの値から選ぶ | if (c) { x = a } else { x = b } |
x = c ? a : b |
三項演算子 |
| null・undefined のときだけ既定値にする | if (v == null) { x = d } else { x = v } |
x = v ?? d |
??(Null合体) |
| 偽の値(0・空文字を含む)なら既定値にする | if (!v) { x = d } else { x = v } |
x = v || d |
||(論理和) |
| 真のときだけ次の値に進む | if (v) { x = f(v) } |
x = v && f(v) |
&&(論理積) |
| 真のときだけ処理を実行する | if (ok) { run() } |
if (ok) run(); |
if文(1行で十分短い) |
| null かもしれない値のプロパティを読む | if (u != null) { x = u.name } |
x = u?.name |
?.(オプショナルチェーン) |
| null かもしれない値を読み、なければ既定値 | 上の2つの組み合わせ | x = u?.name ?? '名前なし' |
?. と ?? |
| 未設定のときだけ代入する | if (o.a == null) { o.a = d } |
o.a ??= d |
??=(Null合体代入) |
| 値に応じて3つ以上に分ける | if ... else if ... else |
マップ M[key] ?? d または早期return |
オブジェクト/関数 |
判断の目安は次の3つです。
- 「AかBか」を選ぶなら三項演算子。
- 「値がなければ既定値」なら
??(0 や空文字も「値がない」とみなしたいときだけ||)。 - 「nullかもしれないものの中身を読む」なら
?.。
三項演算子で value ? value : '既定' や user ? user.name : undefined と書いている箇所は、専用の演算子に置き換えると短くなり、意図も伝わりやすくなります。
0や空文字で結果が変わる:値別の実行結果
v ? v : '既定'、v || '既定'、v ?? '既定' は似ていますが、0・空文字・false・NaN を渡したときの結果が違います。 10種類の値で実行して比べました。
// values.mjs(Node.js v22.22.2 で実行)
const vals = [0, '', null, undefined, NaN, false, 'abc', 5, [], {}];
const show = v =>
v === '' ? "''"
: Array.isArray(v) ? '[]'
: typeof v === 'object' && v !== null ? '{}'
: String(v);
for (const v of vals) {
const a = v ? v : '既定'; // 三項演算子(真偽で判定)
const b = v || '既定'; // 論理和
const c = v ?? '既定'; // Null合体
const d = v && 'あり'; // 論理積
const e = v != null ? v : '既定'; // 三項演算子(nullだけ判定)
console.log([show(v), show(a), show(b), show(c), show(d), show(e)].join(' | '));
}
実行結果を表に整理したものが次です(出力の並びをそのまま列にしています)。
入力 v |
v ? v : '既定' |
v || '既定' |
v ?? '既定' |
v && 'あり' |
v != null ? v : '既定' |
|---|---|---|---|---|---|
0 |
既定 | 既定 | 0 | 0 | 0 |
'' |
既定 | 既定 | '' | '' | '' |
null |
既定 | 既定 | 既定 | null | 既定 |
undefined |
既定 | 既定 | 既定 | undefined | 既定 |
NaN |
既定 | 既定 | NaN | NaN | NaN |
false |
既定 | 既定 | false | false | false |
'abc' |
abc | abc | abc | あり | abc |
5 |
5 | 5 | 5 | あり | 5 |
[] |
[] | [] | [] | あり | [] |
{} |
{} | {} | {} | あり | {} |
この表から読み取れることは3つあります。
v ? v : '既定'とv || '既定'は全行で同じ結果です。前者は後者に置き換えられます。v ?? '既定'とv != null ? v : '既定'も全行で同じ結果です。null と undefined のときだけ既定値になります。v && 'あり'は、偽の値のとき その偽の値自身(0 や '' や NaN)を返します。false が返るわけではありません。
在庫数0が「未入力」になる例
よくあるつまずきが、数値の 0 を「値なし」と判定してしまうケースです。
// stock.mjs(Node.js v22.22.2 で実行)
const stock = 0; // 在庫数は 0 個(正しく入力されている)
console.log('|| :', stock || '未入力');
console.log('?? :', stock ?? '未入力');
console.log('?: :', stock ? stock : '未入力');
console.log('?: null:', stock != null ? stock : '未入力');
実行結果:
|| : 未入力
?? : 0
?: : 未入力
?: null: 0
在庫数 0 は意味のある値なので、「未入力」と表示されると利用者が戸惑います。数値・空文字・false が正当な値として入りうる項目では ?? を使うと、0 がそのまま表示されます。逆に「空文字のときも既定の文言を出したい」(例:ニックネーム未入力なら「ゲスト」)という場面では || が合っています。どちらが正しいというより、「何を値なしとみなすか」で選びます。
| 値なしとみなしたいもの | 使う演算子 |
|---|---|
| null と undefined だけ | ?? |
| null・undefined・0・空文字・false・NaN すべて | || |
| 独自の条件(負の数、空配列など) | 三項演算子で条件を明示:n >= 0 ? n : 0 |
?. は「nullなら undefined を返す」三項演算子の短縮
オプショナルチェーン ?. は、左側が null か undefined なら、エラーにせず undefined を返します。
// optional.mjs(Node.js v22.22.2 で実行)
const user = { profile: null };
console.log(user.profile ? user.profile.name : undefined); // 三項演算子
console.log(user.profile?.name); // ?.
console.log(user.profile?.name ?? '名前なし'); // ?. と ??
try {
console.log(user.profile.name); // そのまま読む
} catch (e) {
console.log(e.name + ': ' + e.message);
}
実行結果:
undefined
undefined
名前なし
TypeError: Cannot read properties of null (reading 'name')
a ? a.b : undefined の形を見つけたら a?.b に置き換えられます。関数呼び出し(cfg.onDone?.())や添字(cfg.list?.[0])にも使え、どちらも対象がなければ undefined になります。
なお、厳密には a ? a.b : undefined と a?.b は同じではありません。前者は a が 0 や空文字のときも undefined を返しますが、後者は null・undefined のときだけです。オブジェクトか null かを判定している場面では同じ結果になります。
&& で「真のときだけ」を書くときの注意
: 以降が不要なとき、条件 && 値 と書く方法があります。ただし、前の表のとおり && は偽のとき 左側の値そのもの を返します。
// and.mjs(Node.js v22.22.2 で実行)
const items = [];
console.log(items.length && `${items.length}件`);
console.log(items.length > 0 ? `${items.length}件` : '');
実行結果(2行目は空文字なので空行になります):
0
1行目は 0 が出力されています。React などの画面描画でこの形を使うと、画面に「0」という文字が表示されることがあります。左側を必ず真偽値にする(items.length > 0 &&)か、三項演算子で偽のときの値を明示すると、意図どおりの結果になります。
複数条件の書き方(&&・||との組み合わせ)
条件部分に && や || を書く
「かつ」「または」を含む条件は、? の前にそのまま書けます。
const age = 20;
const hasTicket = true;
// かつ(両方が真)
const canEnter = age >= 18 && hasTicket ? '入場できます' : '入場できません';
// または(どちらかが真)
const day = 'sat';
const isHoliday = day === 'sat' || day === 'sun' ? '休日' : '平日';
&& と || は三項演算子より優先順位が高いので、かっこがなくても条件全体が先に評価されます。ただ、条件が長くなったら、条件に名前を付けて変数に出すと読みやすくなります。
// 直す前:条件が長く、どこまでが条件か読み取りにくい
const price = user && user.isMember && cart.total >= 5000 && !coupon.expired ? 0 : 500;
// 直した後:条件に名前を付ける
const isFreeShipping = user?.isMember && cart.total >= 5000 && !coupon.expired;
const price2 = isFreeShipping ? 0 : 500;
?? と || ・&& は、かっこなしで混ぜられない
?? を || や && と同じ式に書くときは、かっこが必須です。かっこなしで書くと構文エラーになります(MDN の演算子の優先順位のページにも、グループ化なしでは混在できないと書かれています)。
const a = null ?? 1 || 2;
SyntaxError: Unexpected token '||'
const a = (null ?? 1) || 2;
console.log(a); // 1
三項演算子と ?? を組み合わせる場合はエラーにはなりませんが、?? のほうが先に評価される点を覚えておくと安心です(後述の優先順位の表を参照)。
3つ以上に分ける:ネストした三項演算子の整形
三項演算子は「右から」結び付く
三項演算子を続けて書くと、else if のように3つ以上へ分岐できます。
// nest.mjs(Node.js v22.22.2 で実行)
const score = 75;
const g1 = score >= 80 ? 'A' : score >= 60 ? 'B' : 'C';
const g2 = score >= 80 ? 'A' : (score >= 60 ? 'B' : 'C');
console.log(g1, g2);
実行結果:
B B
g1 と g2 が同じ結果になっているとおり、JavaScriptの三項演算子は 右結合(右側から先にまとまる)です。a ? b : c ? d : e は a ? b : (c ? d : e) と解釈されます。MDN でも、右結合であるため if … else if … else の連鎖のように書けると説明されています。
読みやすい整形:1条件1行にそろえる
1行に詰め込むと、どの : がどの ? に対応するのか追いにくくなります。「条件 ? 値」を1行ずつ縦に並べ、最後の行に既定値を置く形にすると、if / else if / else と同じ感覚で読めます。
// 直す前:1行に詰め込んだ形
const rank = score >= 90 ? 'S' : score >= 80 ? 'A' : score >= 60 ? 'B' : 'C';
// 直した後:1条件1行(MDN に載っている条件の連鎖と同じ形)
const rank2 =
score >= 90 ? 'S'
: score >= 80 ? 'A'
: score >= 60 ? 'B'
: 'C';
フォーマッターの Prettier を使っている場合は、ネストした三項演算子を自動で複数行に整形してくれます(整形後の細かな字下げはバージョンによって異なります。今回 Prettier は未実行です)。
読みやすさを保つ3つの目安
| 目安 | 理由 |
|---|---|
ネストは「: の側」だけにする |
a ? (b ? 1 : 2) : 3 のように ? の側に入れ子を作ると、上から順に読めなくなる |
| 条件は同じ種類の比較でそろえる | score >= 90、score >= 80 … と同じ変数の比較が並ぶと表のように読める |
| 3段を超えたら別の書き方を検討する | マップ・早期return・switch のほうが追加や変更がしやすい |
ESLint には、ネストした三項演算子を検出する no-nested-ternary、x ? true : false のような不要な三項演算子を検出する no-unneeded-ternary というルールがあります。チームで基準をそろえたいときに役立ちます(今回 ESLint は未実行です)。
ネストを別の書き方に置き換える
ネストが深くなってきたら、次の3つの置き換えを検討します。同じ「状態に応じた表示名を返す」処理で並べて比べます。
置き換え1:オブジェクトのマップ(値と値の対応)
「ある値のとき、この値」という対応表になっている分岐は、オブジェクトに置き換えられます。
// 直す前:三項演算子のネスト
const text =
status === 'pending' ? '受付中'
: status === 'shipped' ? '発送済み'
: status === 'done' ? '完了'
: '不明';
// map.mjs(Node.js v22.22.2 で実行)
const status = 'shipped';
const LABELS = { pending: '受付中', shipped: '発送済み', done: '完了' };
console.log(LABELS[status] ?? '不明');
実行結果:
発送済み
対応が増えてもオブジェクトに1行足すだけで済み、条件式を読む必要がなくなります。既定値は ?? で付けます。
ここで1つ注意点があります。ふつうのオブジェクトは toString などのメソッドを受け継いでいるため、キーに外部からの入力を使うと、想定していない値が取れることがあります。
console.log(LABELS['toString'] ?? '不明');
console.log(typeof LABELS['toString']);
console.log(Object.hasOwn(LABELS, 'toString') ? LABELS['toString'] : '不明');
const M = new Map([['pending', '受付中'], ['shipped', '発送済み']]);
console.log(M.get('toString') ?? '不明');
実行結果:
[Function: toString]
function
不明
不明
1行目では「不明」ではなく関数が返っています。キーが利用者の入力やURLなど外から来る場合は、Object.hasOwn() で確認するか、Map を使うと、登録したキーだけが対象になります。
置き換え2:早期return(範囲の判定)
「80以上」「60以上」のような範囲の判定はマップにしにくいので、関数に切り出して早期return(条件に合ったらその場で return する書き方)にします。
// early-return.mjs(Node.js v22.22.2 で実行)
function grade(score) {
if (score >= 80) return 'A';
if (score >= 60) return 'B';
return 'C';
}
console.log([95, 75, 10].map(grade));
実行結果:
[ 'A', 'B', 'C' ]
else を書かなくても、return した時点で関数を抜けるので、上から順に「当てはまったら終わり」と読めます。途中にログ出力や入力チェックを足したくなったときも、行を挿入するだけで済みます。
置き換え3:処理が違うなら「関数のマップ」
値ではなく処理を切り替えたい場合は、関数を値に持つオブジェクトにします。
const HANDLERS = {
add: (a, b) => a + b,
sub: (a, b) => a - b,
mul: (a, b) => a * b,
};
function calc(op, a, b) {
const fn = Object.hasOwn(HANDLERS, op) ? HANDLERS[op] : null;
return fn ? fn(a, b) : NaN;
}
どれを選ぶかの判断表
| 分岐の形 | 向いている書き方 | 例 |
|---|---|---|
| 2択で、値を選ぶだけ | 三項演算子 | isOpen ? '閉じる' : '開く' |
| 3択で、同じ変数の比較が並ぶ | 三項演算子(1条件1行) | 点数からランクを決める |
| 値と値の1対1対応が3つ以上 | オブジェクトのマップ/Map | ステータスコードから表示名 |
| 範囲の判定が3つ以上、途中に処理がある | 関数+早期return | 入力チェックを含む判定 |
| 値ごとに複数行の処理を実行する | switch文/関数のマップ | 操作の種類ごとの処理 |
| 真のときだけ副作用を実行する | if文 | if (ok) save(); |
三項演算子は「値を選ぶ」ための道具です。関数呼び出しや代入といった副作用(値を返す以外の働き)を切り替える用途には if文が向いています。ok ? save() : cancel(); と書くこともできますが、戻り値を使わないなら if / else のほうが「処理を分けている」ことが一目で伝わります。
優先順位で想定と違う結果になる例と直し方
三項演算子は、ほとんどの演算子より優先順位が低い演算子です。これが + や代入と混ぜたときのつまずきにつながります。
優先順位の一覧(MDNの表から抜粋)
MDN の Operator precedence(演算子の優先順位)の表から、三項演算子と一緒に使うことが多いものを抜き出しました。数字が大きいほど先に評価されます。
| 優先順位 | 演算子 | 結合の向き |
|---|---|---|
| 17 | メンバーアクセス .、オプショナルチェーン ?.、関数呼び出し |
左から右 |
| 12 | 乗算・除算・剰余 | 左から右 |
| 11 | 加算 +・減算 - |
左から右 |
| 9 | 比較 < <= > >= |
左から右 |
| 8 | 等価 === !== == != |
左から右 |
| 4 | 論理積 && |
左から右 |
| 3 | 論理和 ||、Null合体 ?? |
左から右 |
| 2 | 三項演算子 ? :、代入 = += など、アロー => |
右から左 |
| 1 | カンマ , |
左から右 |
三項演算子より下にあるのはカンマだけです。つまり、+ も比較も && も ?? も、すべて三項演算子より先に評価されます。
つまずき1:文字列連結 + と混ぜる
// precedence.mjs(Node.js v22.22.2 で実行)
const count = 3;
console.log('合計: ' + count > 0 ? 'あり' : 'なし');
console.log('合計: ' + (count > 0 ? 'あり' : 'なし'));
実行結果:
なし
合計: あり
1行目は「合計: あり」を期待したのに、「合計: 」の部分が消え、しかも「なし」になっています。評価の順番を追うと理由がわかります。
+(優先順位11)が最初に評価され、'合計: ' + countが'合計: 3'になる。>(優先順位9)が評価され、'合計: 3' > 0になる。文字列'合計: 3'は数値に変換できず NaN になり、NaN との比較は false。- 最後に三項演算子が評価され、条件が false なので
'なし'が返る。
次の例は、条件が比較を含まない場合です。
const n = 0;
console.log('件数: ' + n ? 'A' : 'B');
console.log('件数: ' + (n ? 'A' : 'B'));
実行結果:
A
件数: B
'件数: ' + n が先に '件数: 0' という空でない文字列(真)になるため、n が 0 でも常に 'A' が返ります。文字列連結の中で三項演算子を使うときは、三項演算子全体をかっこで囲みます。 テンプレートリテラルを使えば、${} が区切りになるのでこの問題は起きません。
console.log(`合計: ${count > 0 ? 'あり' : 'なし'}`); // 合計: あり
つまずき2:数値の計算と混ぜる
const n = 0;
console.log(10 + (n ? 1 : 2) * 3, 10 + n ? 1 : 2 * 3);
実行結果:
16 1
かっこを付けた左側は 10 + 2 * 3 で 16 になります。かっこのない右側は (10 + n) ? 1 : (2 * 3) と解釈され、10 + 0 が真なので 1 になります。計算式の一部として使うときも、かっこで囲むのが確実です。
つまずき3:代入と混ぜる
: の後ろに代入を書くと、代入は「偽のときの式」の一部として扱われます。
// assign.mjs(Node.js v22.22.2 で実行)
let x = 1, y = 2;
const r = true ? x : y = 99;
console.log(r, x, y);
const r2 = false ? x : y = 99;
console.log(r2, x, y);
実行結果:
1 1 2
99 1 99
true ? x : y = 99 は (true ? x : y) = 99 ではなく、true ? x : (y = 99) と解釈されます。条件が真のときは y = 99 が実行されず、偽のときだけ y が書き換わります。「選んだほうの変数に代入する」つもりで書くと、想定と違う動きになります。
代入先を条件で切り替えたいときは if文で書くと、意図がそのまま伝わります。
// 意図:条件によって x か y のどちらかに 99 を入れる
if (cond) {
x = 99;
} else {
y = 99;
}
逆に、代入する「値」を条件で切り替えるのは三項演算子の得意な形で、かっこも不要です。
total += isMember ? 0 : 500; // total += (isMember ? 0 : 500)
const fee = isMember ? 0 : 500; // 代入は三項演算子の後に行われる
なお、const で宣言した変数に ? の中で代入すると、実行時に次のエラーになります。
const k = 1;
k > 0 ? k = 5 : 0;
TypeError: Assignment to constant variable.
つまずき4:ほかの言語と結合の向きが違う
PHP の三項演算子は、JavaScriptと結合の向きが異なることで知られています(PHP 8 以降は、かっこなしのネストがエラーになります。PHP での動作は今回未実行です)。JavaScriptで左結合と同じ形をかっこで再現すると、結果が変わることを確認できます。
const score = 75;
const left = (score >= 80 ? 'A' : score >= 60) ? 'B' : 'C';
console.log(left);
実行結果:
B
この例ではたまたま同じ 'B' ですが、score が 95 のときは ('A') ? 'B' : 'C' となり 'B' が返ります(右結合なら 'A')。複数の言語を行き来する場合は、ネストにかっこを付けておくと、どの言語でも同じ意味で読めます。
優先順位のつまずきを防ぐチェックリスト
| チェック項目 | 対処 |
|---|---|
+ で文字列や数値とつないでいる |
三項演算子全体を ( ) で囲む。またはテンプレートリテラルにする |
: の後ろに = がある |
if文に書き換える。または x = c ? a : b の形にする |
?? と ||・&& が同じ式にある |
かっこでグループ化する(かっこなしは構文エラー) |
条件に && || が3つ以上ある |
条件を変数に出して名前を付ける |
ネストが ? の側に入っている |
: の側だけにネストする形に並べ替える |
そのほかの書き方と整理のコツ
? true : false は省ける
比較の結果はもともと真偽値なので、? true : false を付ける必要はありません。
const age = 20;
const isAdult = age >= 18 ? true : false;
console.log(isAdult, age >= 18); // true true
真偽値でない値を真偽値にしたいときは、Boolean(value) または !!value と書けます。反転させたいときは ! を使います(cond ? false : true は !cond)。
未設定のときだけ代入する ??= と ||=
「値がなければ既定値を入れる」を三項演算子で書いた opt.a = opt.a != null ? opt.a : 10 は、論理代入演算子で短くできます。
// logical-assign.mjs(Node.js v22.22.2 で実行)
const opt = { size: 0, name: '' };
opt.size ||= 10; // 偽の値なら代入(0 は偽なので 10 になる)
opt.name ??= '無題'; // null・undefined なら代入('' はそのまま)
opt.color ??= 'red'; // 未定義なので代入される
console.log(opt);
実行結果:
{ size: 10, name: '', color: 'red' }
ここでも ||= と ??= の違いは、0 と空文字の扱いです。
真と偽で型をそろえる
三項演算子の2つの値は、型が違っていてもエラーにはなりません。ただ、受け取る側の処理が複雑になるので、同じ型にそろえると扱いやすくなります。
// 直す前:数値か文字列かが条件で変わる
const width = isAuto ? 'auto' : 100;
// 直した後:どちらも文字列
const width2 = isAuto ? 'auto' : '100px';
TypeScript では、前者は string | number という型になり、使う側で型の絞り込みが必要になります。
アロー関数でオブジェクトを返すとき
アロー関数の本体に三項演算子を書き、結果がオブジェクトの場合は、そのまま書けます。
const f = ok => ok ? { ok: true } : { ok: false };
console.log(f(1)); // { ok: true }
ok => { ok: true } のように、本体が直接オブジェクトリテラルになるときは ( ) で囲む必要がありますが、三項演算子の値として書く場合は式の途中なので、囲まなくても正しく解釈されます。
改行の入れ方
1行が長くなる場合は、? と : を行頭に置いて改行すると、真の値と偽の値が縦に並んで読みやすくなります。
const message = isLoggedIn && user.hasUnreadNotifications
? `${user.name}さん、新着のお知らせが${user.unreadCount}件あります`
: 'お知らせはありません';
つまずきやすいポイントの一覧
ここまでの内容を、症状から引ける表にまとめます。
| 症状 | 原因 | 直し方 |
|---|---|---|
| 0 を入れたのに既定値になる | v ? v : d や v || d は 0 を偽とみなす |
v ?? d にする |
| 空文字が既定値に置き換わる | 同上 | v ?? d にする(空文字も置き換えたいなら || のまま) |
| 画面に「0」と表示される | list.length && ... が 0 を返す |
list.length > 0 && ... または三項演算子にする |
| 文字列の前半が消える | '文字' + 条件 ? a : b で + が先に評価される |
三項演算子を ( ) で囲む |
| 常に同じ値になる | 同上(連結した文字列が常に真) | 同上。またはテンプレートリテラル |
SyntaxError: Unexpected token 'if' |
? : の後ろに文を書いた |
式に直すか、全体を if文にする |
SyntaxError: Unexpected token ';' |
: 以降を省略した |
偽のときの値を書く。不要なら if文 |
SyntaxError: Unexpected token '||' |
?? と || をかっこなしで混ぜた |
(a ?? b) || c のようにかっこを付ける |
TypeError: Cannot read properties of null |
null のプロパティを読んだ | a?.b または a ? a.b : 既定値 |
| マップから関数が返ってくる | toString など受け継いだプロパティに当たった |
Object.hasOwn() で確認するか Map を使う |
よくある質問
Q. 三項演算子と if文は、どちらが速いですか?
A. 実用上、速度の差を気にする必要はありません。選ぶ基準は読みやすさです。「値を選ぶ」なら三項演算子、「処理を分ける」なら if文、という役割で使い分けると、コードの意図が伝わりやすくなります。
Q. 三項演算子の中で複数の処理を実行できますか?
A. カンマ演算子を使えば cond ? (a(), b()) : c() のように書けますが、カンマ演算子は優先順位が最も低く、読み手が意図をつかみにくくなります。複数の処理を実行したい場合は if文で書くのがおすすめです。
Q. value ? value : 既定値 と value ?? 既定値 は同じですか?
A. 同じではありません。前者は 0・空文字・false・NaN のときも既定値になりますが、後者は null と undefined のときだけ既定値になります。前者と同じ結果になるのは value || 既定値 です。本記事の値別の表で10種類の値の結果を比べられます。
Q. ネストした三項演算子は使わないほうがよいですか?
A. 同じ種類の条件を1行ずつ縦に並べた形(: の側だけにネストする形)なら、else if と同じ感覚で読めます。3段を超える場合や、? の側に入れ子ができる場合は、オブジェクトのマップや早期returnに置き換えると変更しやすくなります。チームで基準を決め、ESLint の no-nested-ternary でそろえる方法もあります。
まとめ
- 三項演算子は
条件 ? 真の値 : 偽の値の形で値を選ぶ「式」。代入・return・テンプレートリテラルの中に書け、constで宣言できるのが利点です。 - 「値がなければ既定値」は
??、偽の値すべてを置き換えるなら||。v ? v : dはv || dと同じ結果で、0 や空文字も置き換わります。 - null かもしれない値のプロパティは
?.。a ? a.b : undefinedを短く書けます。 &&は偽のとき左側の値(0 など)をそのまま返すので、左側を真偽値にするか三項演算子で偽の値を明示します。- ネストは「1条件1行」で
:の側だけに。3段を超えたら、オブジェクトのマップ(M[key] ?? 既定値)や早期returnに置き換えます。 - 三項演算子の優先順位はカンマの次に低い。
+や計算と混ぜるときは( )で囲み、:の後ろに代入を書きたくなったら if文にします。 ??と||・&&を同じ式に書くときは、かっこが必須です。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「JavaScript三項演算子の書き方|ネスト・複数条件と??の使い分け」
株式会社RJCでは、JavaScriptの条件分岐の書き方を仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。