JavaScriptの基礎 - 条件分岐
概要
JavaScriptにおける条件分岐は、プログラムの実行フローを制御するための基本的な仕組みである。
条件の真偽に応じて異なる処理を実行することで、複雑なロジックを表現できる。
JavaScriptには、条件分岐を実現するための主要な構文として、if / else if / else文、三項演算子 (条件演算子)、switch文の3つが存在する。
それぞれに適切な使用場面があり、状況に応じて使い分けることが重要である。
また、JavaScriptでは条件式においてあらゆる値が真偽値として評価される。
この特性を、Truthy / Falsy と呼ぶ。
Falsyな値は、false、0、-0、0n、""、null、undefined、NaN の8つに限定されており、
それ以外の値は全てTruthyとして扱われる。
if / else if / else
if 文は、最も基本的な条件分岐構文である。
条件式がTruthyと評価された場合にブロック内の処理を実行し、Falsyの場合は else 節の処理を実行する。
基本構文
if 文の基本的な構文を以下に示す。
if (condition) {
// condition が Truthy の場合に実行される
statement1;
}
else if (condition2) {
// condition2 が Truthy の場合に実行される
statement2;
}
else {
// いずれの条件も Falsy の場合に実行される
statement3;
}
使用例を以下に示す。
const score = 75;
if (score >= 90) {
console.log("優");
}
else if (score >= 80) {
console.log("良");
}
else if (score >= 70) {
console.log("可");
}
else {
console.log("不可");
}
// "可"
else if は任意の数を連鎖できるが、条件は上から順に評価される。
最初にTruthyとなった節のブロックが実行され、以降の条件は評価されない。
ブロックと中括弧
if 文では、中括弧 {} を省略して1行で記述することも文法上は可能である。
しかし、中括弧を省略すると、dangling else問題が発生するリスクがあり、コードの可読性が低下する。
// 中括弧を省略した例 (非推奨)
if (condition) doSomething();
// ネストした場合に問題が発生しやすい
if (a)
if (b)
console.log("a and b");
else
console.log("?"); // このelseはどのifに属するか? (内側のif)
// 中括弧を常に使用する (推奨)
if (a) {
if (b) {
console.log("a and b");
}
}
else {
console.log("not a"); // 意図が明確になる
}
中括弧を常に使用することにより、コードの意図が明確になり、後から文を追加した際のバグを防ぐことができる。
ネストしたif文
if 文はネストして記述できるが、ネストが深くなるとコードの可読性が著しく低下する。
// ネストが深い例 (可読性が低い)
function processUser(user) {
if (user) {
if (user.isActive) {
if (user.isVerified) {
console.log("ユーザー処理を続行");
// 本来の処理
}
}
}
}
このような場合は、ガード節 (早期リターン) パターンを用いてネストを解消することが推奨される。
// ガード節を使用した例 (可読性が高い)
function processUser(user) {
if (!user) return;
if (!user.isActive) return;
if (!user.isVerified) return;
console.log("ユーザ処理を続行");
// 本来の処理
}
ガード節パターンでは、無効な条件を先頭で早期に除外することで、メインロジックをフラットな構造で記述できる。
条件式のパターン
if 文の条件式には、様々なパターンが使用できる。
- 等値比較
- 値の一致を確認する際は、型変換を行わない厳密等価演算子 (
===) を使用することが推奨される。 const status = "active"; if (status === "active") { console.log("アクティブなユーザー"); } // == は型変換を行うため予期しない動作になる可能性がある (非推奨) if (status == "active") { }
- 値の一致を確認する際は、型変換を行わない厳密等価演算子 (
- 範囲チェック
- 数値が特定の範囲内にあるかどうかを確認するパターンである。
const age = 25; if (age >= 18 && age < 65) { console.log("成人 (就労年齢)"); }
- 存在確認
- オブジェクトのプロパティや変数の値が存在するかどうかを確認するパターンである。
const user = { name: "Alice", profile: { bio: "Developer" } }; if (user.profile) { console.log(user.profile.bio); } // null または undefined の両方を除外する厳密な確認 if (user.profile !== null && user.profile !== undefined) { console.log(user.profile.bio); }
- typeof演算子を使用した型チェック
function greet(value) { if (typeof value === "string") { console.log("こんにちは、" + value + "さん"); } else if (typeof value === "number") { console.log("番号: " + value); } else { console.log("不明な値: " + value); } } greet("Alice"); // "こんにちは、Aliceさん" greet(42); // "番号: 42"
三項演算子 (条件演算子)
三項演算子 (条件演算子) は、JavaScriptで唯一の3つのオペランドを取る演算子である。
簡潔な条件分岐を式として記述でき、変数への代入や関数の引数として使用できる。
基本構文
三項演算子の構文は以下の通りである。
condition ? exprIfTrue : exprIfFalse
condition がTruthyの場合は exprIfTrue が評価されて返され、Falsyの場合は exprIfFalse が評価されて返される。
使用例を以下に示す。
const age = 25;
const beverage = age >= 21 ? "Beer" : "Juice";
console.log(beverage); // "Beer"
// 関数の引数での使用
console.log(age >= 20 ? "成人" : "未成年");
// テンプレートリテラル内での使用
const isLoggedIn = true;
const greeting = `ようこそ、${isLoggedIn ? "ユーザー" : "ゲスト"}さん`;
console.log(greeting); // "ようこそ、ユーザーさん"
if文との使い分け
三項演算子と if 文は、使用場面が異なる。
- 三項演算子が適している場合
- 単純な値の決定 (2つの選択肢から1つを選ぶ)
- 変数への代入
- 式として評価される文脈 (テンプレートリテラル、JSXなど)
if文が適している場合- 複数行にわたる処理
- 副作用を伴う処理 (関数呼び出し、ログ出力等)
- 複雑な条件ロジック
// 三項演算子が適切な例: 値の決定
const label = isActive ? "有効" : "無効";
const discount = isMember ? price * 0.8 : price;
// if文が適切な例: 複数行の処理
if (isActive) {
user.activate();
sendWelcomeEmail(user);
console.log("アクティベート完了");
}
else {
user.deactivate();
console.log("無効化完了");
}
ネストした三項演算子
三項演算子はネストして連鎖させることが可能であるが、一般的に非推奨である。
// ネストした三項演算子 (非推奨: 可読性が低い)
const score = 75;
const grade = score >= 90
? "優"
: score >= 80
? "良"
: score >= 70
? "可"
: "不可";
このような複数の条件分岐が必要な場合は、if / else if 文を使用することが推奨される。
// if / else if を使用した例 (推奨: 可読性が高い)
let grade;
if (score >= 90) {
grade = "優";
}
else if (score >= 80) {
grade = "良";
}
else if (score >= 70) {
grade = "可";
}
else {
grade = "不可";
}
switch文
switch 文は、1つの式を複数の値と比較する際に用いる条件分岐構文である。
複数の選択肢がある場合に、if / else if の連鎖よりも可読性が高くなることがある。
基本構文
switch 文の基本的な構文を以下に示す。
switch (expression) {
case value1:
// expression === value1 の場合に実行される
statements;
break;
case value2:
// expression === value2 の場合に実行される
statements;
break;
default:
// いずれのcaseにも一致しない場合に実行される
statements;
}
switch 文は、expressionの値を各caseの値と厳密等価演算子 (===) で比較する。
default 節は省略可能であるが、予期しない値への対処のため記述することが推奨される。
使用例を以下に示す。
const day = "Monday";
switch (day) {
case "Monday":
console.log("月曜日: 週の始まり");
break;
case "Friday":
console.log("金曜日: 週の終わり");
break;
case "Saturday":
case "Sunday":
console.log("週末");
break;
default:
console.log("平日");
}
// "月曜日: 週の始まり"
breakとフォールスルー
switch 文において、break文はブロックの実行を終了して switch 文から抜け出す役割を担う。
break 文を省略すると、フォールスルー (fall-through) が発生し、次のcaseの処理も続けて実行される。
// フォールスルーが発生する例
const num = 1;
switch (num) {
case 1:
console.log("1"); // 実行される
case 2:
console.log("2"); // break がないため続けて実行される
case 3:
console.log("3"); // break がないため続けて実行される
break;
case 4:
console.log("4"); // 実行されない
}
// "1", "2", "3" が全て出力される
フォールスルーは通常バグの原因となるため、意図的に使用する場合はコメントで明示することが推奨される。
// 意図的なフォールスルー (コメントで明示する)
switch (command) {
case "save":
case "write":
// "save" と "write" で同じ処理を行う (意図的なフォールスルー)
saveToFile();
break;
case "quit":
case "exit":
// "quit" と "exit" で同じ処理を行う (意図的なフォールスルー)
closeApplication();
break;
}
switch文の応用
switch 文には、基本的な使い方以外にもいくつかの応用パターンが存在する。
- グルーピング
- 複数の
caseで同じ処理を実行したい場合、caseを連続して並べることでグルーピングできる。 const month = 4; let season; switch (month) { case 3: case 4: case 5: season = "春"; break; case 6: case 7: case 8: season = "夏"; break; case 9: case 10: case 11: season = "秋"; break; default: season = "冬"; } console.log(season); // "春"
- 複数の
switch(true)パターンswitch文の式にtrueを指定し、各caseで条件式を記述するパターンである。- 複雑な条件式を
switch構造で整理したい場合に有用である。 const score = 75; let grade; switch (true) { case score >= 90: grade = "優"; break; case score >= 80: grade = "良"; break; case score >= 70: grade = "可"; break; default: grade = "不可"; } console.log(grade); // "可"
- ブロックスコープ
case節内でlet/constを使用する場合、同じswitchブロック内で変数名が衝突する可能性がある。- この問題を回避するには、
case節を中括弧で囲んで独立したブロックスコープを作成する。 switch (action) { case "create": { const id = generateId(); // このブロック内のみで有効 const item = createItem(id); console.log(item); break; } case "delete": { const id = getSelectedId(); // 別ブロックなので同名でもOK deleteItem(id); break; } }
if文との使い分け
switch 文 と if 文はそれぞれ異なる場面に適している。
下表に比較を示す。
| 観点 | if文 | switch文 |
|---|---|---|
| 比較の種類 | あらゆる条件式 (範囲比較、関数の戻り値など) | 1つの式を複数の値と比較 |
| 比較演算子 | ===、!==、<、>、&&、|| 等、自由に指定 | === のみ (厳密等価) |
| 型の比較 | あらゆる型の条件式 | プリミティブ値との厳密等価 |
| 可読性 | 条件が少ない場合や複雑な条件式に適する | 同一の式を複数の値と比較する場合に適する |
| フォールスルー | なし | あり (breakを省略すると発生) |
| デフォルト処理 | else節 | default節 |
| 適した場面 | 範囲チェック、複雑なロジック、型チェック | 列挙型、固定の文字列・数値との比較 |
// switch文が適した例: 固定の文字列との比較
switch (statusCode) {
case 200: console.log("OK"); break;
case 404: console.log("Not Found"); break;
case 500: console.log("Internal Server Error"); break;
default: console.log("その他");
}
// if文が適した例: 範囲比較
if (temperature >= 35) {
console.log("猛暑");
}
else if (temperature >= 25) {
console.log("夏日");
}
else if (temperature >= 0) {
console.log("通常");
}
else {
console.log("氷点下");
}
Truthy / Falsy
JavaScriptでは、if 文等の条件式においてあらゆる値が自動的に真偽値へ変換される。
この時、false と評価される値をFalsy、true と評価される値をTruthyと呼ぶ。
Falsy値一覧
JavaScriptにおけるFalsy値は以下の8つのみである。
これら以外の全ての値は、Truthyとなる。
| 値 | 型 | 説明 |
|---|---|---|
| false | Boolean | ブール値の false |
| 0 | Number | 数値のゼロ |
| -0 | Number | 負のゼロ |
| 0n | BigInt | BigInt のゼロ |
| "" | String | 空文字列 (長さ0の文字列) |
| null | Null | 値の意図的な欠落 |
| undefined | Undefined | 未定義 (値が代入されていない) |
| NaN | Number | 非数値 (Not a Number) |
// 各Falsy値の確認
if (false) { } // 実行されない
if (0) { } // 実行されない
if (-0) { } // 実行されない
if (0n) { } // 実行されない
if ("") { } // 実行されない
if (null) { } // 実行されない
if (undefined) { } // 実行されない
if (NaN) { } // 実行されない
// Boolean()による明示的な変換で確認
console.log(Boolean(false)); // false
console.log(Boolean(0)); // false
console.log(Boolean("")); // false
console.log(Boolean(null)); // false
console.log(Boolean(undefined)); // false
console.log(Boolean(NaN)); // false
注意が必要なTruthy値
一見、Falsyのように思えるが、実際にはTruthyと評価される値が存在する。
これらはバグの原因になりやすいため、特に注意が必要である。
"0"(文字列のゼロ)- 数値の 0 はFalsyだが、文字列 "0" は空文字列ではないためTruthyである。
"false"(文字列のfalse)- ブール値の false はFalsyだが、文字列 "false" はTruthyである。
[](空の配列)- 空の配列はオブジェクトであるため、Truthyである。
{}(空のオブジェクト)- 空のオブジェクトもTruthyである。
// 注意が必要なTruthy値
console.log(Boolean("0")); // true (文字列 "0" は Truthy)
console.log(Boolean("false")); // true (文字列 "false" は Truthy)
console.log(Boolean([])); // true (空配列は Truthy)
console.log(Boolean({})); // true (空オブジェクトは Truthy)
// よくある誤り
const value = "0";
if (value) {
console.log("Truthy"); // こちらが実行される
}
else {
console.log("Falsy"); // 実行されない
}
// 空配列の確認は、lengthプロパティを使用する
const arr = [];
if (arr.length === 0) {
console.log("空の配列"); // 正しいチェック方法
}
条件分岐での活用
Truthy / Falsyの特性を利用することで、簡潔な条件分岐を記述できる。
const user = getUserFromDB();
// userがnullまたはundefinedでないかを確認
if (user) {
console.log("ユーザーが存在する: " + user.name);
}
// 文字列の存在確認
const message = getMessage();
if (message) {
displayMessage(message);
}
// 配列の存在および要素確認 (要素数も考慮する場合は、lengthを使用)
const items = getItems();
if (items && items.length > 0) {
renderList(items);
}
暗黙の型変換に関する注意点
条件式で暗黙の型変換が行われることにより、意図しない動作が生じる場合がある。
明示的な比較を記述することで、こうした問題を回避できる。
// 暗黙の型変換による問題
function processCount(count) {
// count が 0 の場合、Falsy と判定されて処理がスキップされる
if (count) {
console.log("件数: " + count);
}
}
processCount(0); // 何も出力されない (意図しない動作)
// 明示的な比較で解決する
function processCountFixed(count) {
if (count !== null && count !== undefined) {
console.log("件数: " + count);
}
}
processCountFixed(0); // "件数: 0" (正しく動作する)
// 数値の存在確認には、typeofを使用する
function isValidNumber(value) {
return typeof value === "number" && !isNaN(value);
}
console.log(isValidNumber(0)); // true (0 は有効な数値)
console.log(isValidNumber(null)); // false
console.log(isValidNumber(undefined)); // false
console.log(isValidNumber(NaN)); // false
null と undefined のみを除外したい場合は、Null合体演算子 (??) の使用も検討する。
?? は null と undefined のみをFalsyとして扱うため、0 や "" が有効な値として扱われる。
実用的な条件分岐パターン
実際の開発でよく使用される条件分岐のパターンを以下に示す。
ガード節 (早期リターン)
ガード節 (Guard Clause) は、関数の先頭で無効な条件を早期に除外するパターンである。
ネストを浅く保ち、ソースコードの可読性を向上させることができる。
// ネストが深い例 (避けるべきパターン)
function sendEmail(user) {
if (user) {
if (user.email) {
if (user.isSubscribed) {
if (user.isVerified) {
// メール送信処理
mailer.send(user.email, "Welcome!");
}
}
}
}
}
// ガード節を使用した例 (推奨パターン)
function sendEmail(user) {
if (!user) return;
if (!user.email) return;
if (!user.isSubscribed) return;
if (!user.isVerified) return;
// 全ての条件を満たした場合のメイン処理
mailer.send(user.email, "Welcome!");
}
ガード節パターンのメリットを以下に示す。
- ネストが浅くなりコードが読みやすくなる。
- 各条件の意図が明確になる。
- メインロジックが最後にフラットな形で記述される。
- 単体試験が書きやすくなる。
入力検証パターンにガード節を適用した例を以下に示す。
function createUser(name, age, email) {
// 入力検証 (ガード節)
if (typeof name !== "string" || name.trim() === "") {
throw new Error("名前は必須です");
}
if (typeof age !== "number" || age < 0 || age > 150) {
throw new Error("年齢が無効です");
}
if (typeof email !== "string" || !email.includes("@")) {
throw new Error("メールアドレスが無効です");
}
// 検証を通過した場合のメイン処理
return { name: name.trim(), age, email };
}
オブジェクトルックアップ
オブジェクトルックアップは、switch 文の代替として使用できるパターンである。
キーと値のマッピングをオブジェクトとして定義し、動的にアクセスすることで条件分岐を簡潔に表現できる。
// switch文を使用した場合
function getStatusColor(status) {
switch (status) {
case "success": return "green";
case "warning": return "orange";
case "error": return "red";
case "info": return "blue";
default: return "gray";
}
}
// オブジェクトルックアップを使用した場合
const statusColors = {
success: "green",
warning: "orange",
error: "red",
info: "blue",
};
function getStatusColor(status) {
return statusColors[status] ?? "gray";
}
console.log(getStatusColor("success")); // "green"
console.log(getStatusColor("unknown")); // "gray"
関数をオブジェクトの値として格納することで、より複雑な処理にも対応できる。
// 関数をオブジェクトの値として格納するパターン
const actions = {
increment: (count) => count + 1,
decrement: (count) => count - 1,
reset: (_) => 0,
};
function applyAction(action, count) {
const fn = actions[action];
if (!fn) {
throw new Error("不明なアクション: " + action);
}
return fn(count);
}
console.log(applyAction("increment", 5)); // 6
console.log(applyAction("decrement", 5)); // 4
console.log(applyAction("reset", 5)); // 0
オブジェクトルックアップパターンのメリットを以下に示す。
- ソースコードが簡潔になる。
- 新しい選択肢の追加がオブジェクトへの1行追加で済む。
- オブジェクト自体を外部から注入したり動的に変更することができる。
- フォールスルーの心配がない。
関連情報
- JavaScriptの基礎 - 変数宣言
- let / const / varの宣言方法、スコープ、ホイスティング
- JavaScriptの基礎 - プリミティブ型
- 7つのプリミティブ型、typeof演算子、型の自動変換
- JavaScriptの基礎 - 比較演算子と論理演算子
- === / ==の違い、短絡評価、Null合体演算子、オプショナルチェーン
- JavaScriptの基礎 - 反復処理(for文)
- for文、for...of、for...in、使い分け
- JavaScriptの基礎 - 反復処理(while文)
- while文、do...while文、break / continue、無限ループ回避