javaScript : 條件分歧處理

 


寫 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 裡——退一步,整理語意,讓程式自己把話說清楚。


延伸閱讀推薦:



張貼留言 (0)
較新的 較舊