寫 JS,最常遇到的不是演算法難題,而是分岔口:這裡要不要做?要做哪一條?結果要怎麼回?if/else 能解很多事,但一多就糊成一鍋,switch、三元運算子、短路評估、?? 和 ?.、甚至守門條款與查表設計,都是把邏輯梳乾淨的工具。
這篇不談玄學,只用實際範例告訴你什麼情境用什麼手法,怎麼避免「巢狀地獄」,以及為什麼 ===、falsy 值、NaN 會讓人踩雷。你會看到表單驗證、API 請求、費率計算等常見場景的拆解,從需求列點、守門條款先擋、再選擇最合適的分支方式。最後附上完整步驟與雷點清單,幫你把「看得懂」變成「寫得穩」。希望本篇文章能幫助到需要的您。
目錄
{tocify} $title={目錄}
為什麼需要條件分歧?
程式想做「有條件的選擇」時,就需要條件分歧(conditional branching)。它決定哪段程式碼會被執行、哪段被略過。寫得好,邏輯清楚、錯誤少;寫得亂,Bug 跟維護成本會一起上升。本篇的目標是幫你:
熟悉各種分支手法、2) 知道何時用哪一種、3) 能把巢狀地獄(nested hell)重構回可讀的樣子。
JS 中常用的條件分歧方式
1. if / else if / else:萬用基本款。
2. switch:多分支、以「相等比對」為主的情境。
3. 三元運算子 condition ? A : B:在「表達式」中作簡短二選一。
4. 邏輯運算(短路評估)&&、||:用條件來「接線」後面的表達式。
5. Nullish 合併 ?? 與可選鏈 ?.:對 null/undefined 做防呆分支。
6. 守門條款(Guard Clauses)與早退 return:用「反向條件」提早離開,扁平化巢狀。
7. 查表(Lookup Map)/ 策略(Strategy):用物件或 Map 取代冗長 switch。
8. 非同步情境下的分支(await / Promise):條件要考慮異常、逾時與重試。
if / else:最基礎也是最容易失控的地方
1. 基本語法與小提示
if (score >= 90) {
grade = 'A';
} else if (score >= 80) {
grade = 'B';
} else if (score >= 70) {
grade = 'C';
} else {
grade = 'D';
}
判斷順序要由嚴格到寬鬆,避免覆蓋問題。
獨立條件與互斥條件要分清楚:互斥條件(如分數區間)用 if / else if;獨立條件(可同時成立)用多個 if。
2. 巢狀過深 → 用守門條款扁平化
// 壞例子:巢狀多層
function createOrder(user, cart) {
if (user) {
if (user.isActive) {
if (Array.isArray(cart) && cart.length > 0) {
// ...
}
}
}
}
// 好例子:守門條款早退
function createOrder(user, cart) {
if (!user) throw new Error('無使用者');
if (!user.isActive) throw new Error('帳號未啟用');
if (!Array.isArray(cart) || cart.length === 0) throw new Error('購物車為空');
// …主流程
}
switch:多分支、相等比對的好朋友
1. 基本寫法
switch (command) {
case 'start':
startJob();
break;
case 'stop':
stopJob();
break;
case 'status':
showStatus();
break;
default:
console.warn('未知指令');
}
重點:
記得 break,否則會 fall through(繼續跑到下一個 case)。
當條件是 值相等(或枚舉)時,switch 可比長長的 if/else if 更清楚。
2. Grouping:多個 case 共用邏輯
switch (httpStatus) {
case 200:
case 201:
case 204:
handleSuccess();
break;
case 400:
case 404:
handleClientError();
break;
default:
handleServerOrUnknown();
}
三元運算子:短,但請節制
基本用法
const label = isVip ? 'VIP' : 'USER';
巢狀三元運算子要小心可讀性
// 不建議:巢狀影響可讀性
const level = score >= 90 ? 'A' : score >= 80 ? 'B' : score >= 70 ? 'C' : 'D';
// 建議:改回 if/else 或抽函式
function getLevel(score) {
if (score >= 90) return 'A';
if (score >= 80) return 'B';
if (score >= 70) return 'C';
return 'D';
}
短路評估:用 && / || 接線你的表達式
1. &&:前面為真才執行後方表達式
isLoggedIn && renderUserMenu(); // 只有登入時才渲染
2. ||:前面為假(falsy)就用後方作替代
const displayName = userInput || '訪客';
3. 容易踩雷:|| 會把 0、''、false 當假
如果想針對 僅 null/undefined 才補值,請用 ??。
Nullish 合併 ?? 與可選鏈 ?.:安全又精準的防呆分支
1. ??:只在左邊是 null 或 undefined 時才用右邊
const pageSize = userConfig.pageSize ?? 20; // 0 不會被當成空值
2. ?.:安全取值,避免「讀未定義屬性」
const city = user?.address?.city ?? '未知城市';
守門條款(Guard Clauses):把錯的先擋掉、主流程就乾淨
這手法不是語法,而是一種寫法。概念很簡單:
反向思考條件,把不符合的情況先 return / throw。
主流程就能 少一層縮排,維護者一眼看懂。
function transfer(from, to, amount) {
if (amount <= 0) throw new Error('金額需大於 0');
if (!from || !to) throw new Error('帳戶資訊不完整');
if (from.balance < amount) throw new Error('餘額不足');
// 主流程
from.balance -= amount;
to.balance += amount;
}
查表(Lookup Map)與策略(Strategy):優雅取代巨型 switch
1. 查表:用物件映射行為
const handlers = {
start: () => startJob(),
stop: () => stopJob(),
status: () => showStatus(),
};
(handlers[command] || (() => console.warn('未知指令')))();
優點:新增一個指令 = 新增一個鍵值,不必動到其他分支。
適合:字串或枚舉對應到函式的情境。
2. 策略:抽離判斷 + 行為(更可測)
class ShippingStrategy {
calc() { throw new Error('not implemented'); }
}
class Normal extends ShippingStrategy { calc(weight){ return 80 + weight * 10; } }
class Express extends ShippingStrategy { calc(weight){ return 150 + weight * 15; } }
function getStrategy(type) {
const table = { normal: new Normal(), express: new Express() };
return table[type] ?? new Normal();
}
function fee(type, weight) {
return getStrategy(type).calc(weight);
}
非同步情境的條件分歧:加入失敗路徑與逾時
1. await 與條件
async function fetchUserOrGuest(id) {
try {
const user = await api.getUser(id);
return user ?? { role: 'guest' };
} catch (e) {
return { role: 'guest', reason: 'fetch-failed' };
}
}
2. 逾時控制也是一種分支
function withTimeout(promise, ms = 5000) {
return Promise.race([
promise,
new Promise((_, rej) => setTimeout(() => rej(new Error('timeout')), ms))
]);
}
真值(truthy/falsy)與相等:條件的基礎認知
1. Falsy 值:false, 0, -0, 0n, ''(空字串), null, undefined, NaN。
2. == vs ===:
== 會做型別轉換,容易出事。
建議預設用 === 與 !==。
3. NaN 不等於任何東西(包含自己),判斷用 Number.isNaN(x)。
DOM / 表單實例:條件分歧在前端的落地
<input id="email">
<button id="submit">送出</button>
<div id="msg"></div>
<script>
const email = document.getElementById('email');
const msg = document.getElementById('msg');
document.getElementById('submit').addEventListener('click', () => {
const v = email.value.trim();
// 守門條款
if (!v) return show('Email 不可為空');
if (!/^\S+@\S+\.\S+$/.test(v)) return show('Email 格式不正確');
show('驗證成功,送出中…');
// …送 API
});
function show(text) { msg.textContent = text; }
</script>
Node.js 實例:API 請求的條件處理
import express from 'express';
const app = express();
app.use(express.json());
app.post('/orders', (req, res) => {
const { userId, items } = req.body;
// 守門條款
if (!userId) return res.status(400).json({ error: '缺少 userId' });
if (!Array.isArray(items) || items.length === 0) {
return res.status(400).json({ error: 'items 不可為空' });
}
// 依角色決定折扣(查表)
const user = getUser(userId);
const discountTable = { vip: 0.9, staff: 0.8, guest: 1.0 };
const rate = discountTable[user.role] ?? 1.0;
const total = items.reduce((s, it) => s + it.price * it.qty, 0) * rate;
return res.json({ ok: true, total });
});
app.listen(3000);
選擇哪一種分支?實務決策準則
1. 兩路二選一、很短 → 三元運算子
2. 多個互斥選項、值相等比對 → switch 或查表
3. 條件很複雜、需要敘事性 → if/else + 守門條款
4. 僅在存在時才做事 / 補預設值 → &&、||、??、?.
5. 邏輯擴充會變多 → 查表 / 策略模式
6. 流程容易失敗 → try/catch + 逾時/重試分支
實作步驟:把一段亂碼重構成清楚分支
情境:計算運費,需求持續演進
條件:商品重量、配送方式、是否冷藏、是否海外、是否 VIP。
目標:避免巢狀、讓規則可擴充。
1. 先用「守門條款」擋掉不合理輸入
function calcShipping({ weight, method, isCold, isOverseas, isVip }) {
if (weight <= 0) throw new Error('重量需大於 0');
if (!['normal', 'express'].includes(method)) throw new Error('未知配送法');
// ...
}
2. 把「特殊附加費」拆成獨立小函式(單一職責)
function coldFee(isCold) { return isCold ? 100 : 0; }
function overseasFee(isOverseas, base) { return isOverseas ? base * 0.5 : 0; }
function vipDiscount(isVip, subtotal) { return isVip ? subtotal * 0.9 : subtotal; }
3. 核心費率用「查表」管理(易於擴充)
const baseTable = {
normal: (w) => 80 + w * 10,
express: (w) => 150 + w * 15
};
4. 串起來(條件分歧最小化、語意清晰)
function calcShipping(opts) {
const { weight, method, isCold, isOverseas, isVip } = opts;
if (weight <= 0) throw new Error('重量需大於 0');
const baseCalc = baseTable[method];
if (!baseCalc) throw new Error('未知配送法');
let subtotal = baseCalc(weight);
subtotal += coldFee(isCold);
subtotal += overseasFee(isOverseas, subtotal);
const total = vipDiscount(isVip, subtotal);
return Math.round(total);
}
常見錯誤與雷點
1. 把指定 = 寫成比較 ===(或反之)
if (x = 1) { /* 永遠為真,因為賦值結果是 1 */ } // ❌
if (x === 1) { /* 正確比較 */ } // ✅
2. 忘記 switch 的 break,造成意外落空
switch (t) {
case 'A': doA();
case 'B': doB(); // 'A' 會繼續跑到 'B' // ❌
}
// 加上 break 或故意 group 才安全 // ✅
3. 把位元運算子 &、| 當成邏輯運算子 &&、||
if (a & b) {} // 位元運算,非布林 // ❌
if (a && b) {} // 布林短路 // ✅
4. 用 || 當預設值,卻把合法的 0/空字串吃掉
const n = userInput || 10; // 輸入 0 會被換成 10 // ❌ const n2 = userInput ?? 10; // 只在 null/undefined 才補 10 // ✅
5. 拿 NaN 直接比較
if (value === NaN) {} // 永遠為假 // ❌
if (Number.isNaN(value)) {} // ✅
6. if (obj.prop) 當存在檢查,卻誤殺合法 falsy
if (user.age) { /* age=0 會被當不存在 */ } // ❌
if ('age' in user) { /* 欄位存在即為真 */ } // ✅
7. 巢狀三元過度使用
一旦判斷超過一層,幾乎都應回到 if/else 或抽函式。
8. switch(true) 濫用
switch (true) {
case score >= 90: ... // 可讀性差 // ❌
}
// 用 if/else 更直覺 // ✅
9. 在 try/catch 中吞錯誤,不做回應分支
try { await save(); } catch (e) {} // 靜默失敗 // ❌
try { await save(); } catch (e) { showError(e.message); } // ✅
10. 非同步條件忘了等結果
if (api.check()) { /* 這裡 check() 是 Promise */ } // ❌
if (await api.check()) { /* ✅ */ }
11. 把型別轉換當作理所當然
用 ===,不要賭 == 的隱式轉換。
12. 條件過長不抽取
if (a > 10 && b.startsWith('x') && !isHoliday(date) && isActive(user)) {}
// 抽出命名變數或小函式,讓條件可讀 // ✅
結語
條件分歧不是只有 if/else。當你懂得把「例外」早退、把「選項」查表、把「短邏輯」交給短路與三元、再用 ?? 與 ?. 打好安全網,程式會變得清楚、易擴充、又耐操。
遇到複雜邏輯,別硬撐在一坨 if 裡——退一步,整理語意,讓程式自己把話說清楚。
