Skip to content

Switch 语句:优雅处理多分支选择

在一个自动售货机前,你投入硬币后按下按钮 A1 得到可乐,按下 B2 得到薯片,按下 C3 得到巧克力。售货机会根据你按下的按钮,精确地分发对应的商品。这种"一个输入对应一个输出"的多分支选择模式,在编程中就是 switch 语句最擅长的场景。

为什么需要 Switch 语句

当你需要根据一个变量的不同值执行不同代码时,可以使用 if-else if 链。但如果分支很多,这种方式会变得冗长:

javascript
let day = "Monday";

if (day === "Monday") {
  console.log("Start of work week");
} else if (day === "Tuesday") {
  console.log("Second day of work");
} else if (day === "Wednesday") {
  console.log("Midweek");
} else if (day === "Thursday") {
  console.log("Almost Friday");
} else if (day === "Friday") {
  console.log("Last work day!");
} else if (day === "Saturday") {
  console.log("Weekend!");
} else if (day === "Sunday") {
  console.log("Rest day");
}

这时 switch 语句就显得更加清晰和易读:

javascript
let day = "Monday";

switch (day) {
  case "Monday":
    console.log("Start of work week");
    break;
  case "Tuesday":
    console.log("Second day of work");
    break;
  case "Wednesday":
    console.log("Midweek");
    break;
  case "Thursday":
    console.log("Almost Friday");
    break;
  case "Friday":
    console.log("Last work day!");
    break;
  case "Saturday":
    console.log("Weekend!");
    break;
  case "Sunday":
    console.log("Rest day");
    break;
}

Switch 语句的基本结构

switch 语句的语法包含几个关键部分:

javascript
switch (expression) {
  case value1:
    // 当 expression === value1 时执行
    break;
  case value2:
    // 当 expression === value2 时执行
    break;
  default:
  // 当没有匹配的 case 时执行
}

程序首先计算 switch 后括号中的表达式,然后将结果与每个 case 后的值进行严格相等比较(使用 ===)。找到第一个匹配的 case 后,执行其对应的代码块,直到遇到 break 语句或 switch 语句结束。

让我们看一个实际的例子,根据用户的会员等级提供不同的服务:

javascript
let membershipLevel = "gold";

switch (membershipLevel) {
  case "bronze":
    console.log("5% discount on all purchases");
    break;
  case "silver":
    console.log("10% discount on all purchases");
    console.log("Free shipping on orders over $50");
    break;
  case "gold":
    console.log("15% discount on all purchases");
    console.log("Free shipping on all orders");
    console.log("Priority customer support");
    break;
  case "platinum":
    console.log("20% discount on all purchases");
    console.log("Free shipping on all orders");
    console.log("Priority customer support");
    console.log("Exclusive early access to new products");
    break;
  default:
    console.log("No membership benefits");
    console.log("Join our membership program today!");
}

在这个例子中,membershipLevel"gold",所以程序会执行 case "gold" 下的三句输出,然后因为 break 语句而跳出整个 switch 结构。

Break 语句的重要性

break 语句用于终止 switch 语句的执行。如果省略 break,程序会继续执行下一个 case 的代码,这种行为叫做"fall-through"(穿透)。

这通常不是你想要的:

javascript
let grade = "B";

switch (grade) {
  case "A":
    console.log("Excellent!");
  // 没有 break,会继续执行
  case "B":
    console.log("Good job!");
  // 没有 break,会继续执行
  case "C":
    console.log("You passed.");
  // 没有 break,会继续执行
  case "D":
    console.log("Needs improvement.");
    break;
  case "F":
    console.log("Failed.");
    break;
}

// 输出:
// Good job!
// You passed.
// Needs improvement.

grade"B" 时,程序从 case "B" 开始执行,但由于没有 break,它会继续执行 case "C"case "D" 的代码,直到遇到 break 才停止。这种行为很少是我们想要的,所以几乎每个 case 都应该以 break 结束。

最后一个 casedefault 后的 break 在技术上是可选的(因为已经到末尾了),但为了保持一致性和避免将来添加新 case 时忘记加 break,建议总是加上。

Fall-through 的妙用

虽然 fall-through 通常是要避免的,但在某些情况下,它恰恰是一种优雅的解决方案。当多个 case 需要执行相同的代码时,可以利用 fall-through:

javascript
let month = "February";
let days;

switch (month) {
  case "January":
  case "March":
  case "May":
  case "July":
  case "August":
  case "October":
  case "December":
    days = 31;
    break;
  case "April":
  case "June":
  case "September":
  case "November":
    days = 30;
    break;
  case "February":
    days = 28; // 简化版,不考虑闰年
    break;
  default:
    days = 0;
    console.log("Invalid month");
}

console.log(`${month} has ${days} days`); // February has 28 days

这里,多个月份可以共享同一段设置天数的代码。程序检查 month 是否等于 "January",如果不是,就检查下一个 case,以此类推。当找到 "February" 时,执行 days = 28break

还可以为这种分组添加注释,让意图更清晰:

javascript
switch (fruit) {
  case "apple":
  case "pear":
  case "peach":
    // 温带水果
    console.log("Temperate climate fruit");
    break;
  case "banana":
  case "mango":
  case "papaya":
    // 热带水果
    console.log("Tropical fruit");
    break;
  case "orange":
  case "lemon":
  case "grapefruit":
    // 柑橘类水果
    console.log("Citrus fruit");
    break;
}

Default 子句

default 子句是可选的,但强烈建议添加。它类似于 if-else 链中的最后一个 else,当所有 case 都不匹配时执行:

javascript
let userInput = "start";

switch (userInput) {
  case "start":
    console.log("Starting the program...");
    break;
  case "stop":
    console.log("Stopping the program...");
    break;
  case "pause":
    console.log("Pausing the program...");
    break;
  default:
    console.log("Unknown command. Valid commands: start, stop, pause");
}

default 子句可以放在任何位置,但习惯上放在最后。如果放在中间,仍然需要 break 语句:

javascript
switch (value) {
  case 1:
    console.log("One");
    break;
  default:
    console.log("Other");
    break; // 需要 break,否则会继续执行 case 2
  case 2:
    console.log("Two");
    break;
}

不过,把 default 放在最后是更清晰的做法。

Switch 语句的严格比较

需要特别注意的是,switch 使用严格相等比较(===),不会进行类型转换:

javascript
let value = "1";

switch (value) {
  case 1:
    console.log("Number one");
    break;
  case "1":
    console.log("String one"); // 这会执行
    break;
}

// 输出: String one

如果 value 是数字 1,会匹配第一个 case。但这里 value 是字符串 "1",所以匹配第二个 case。这和 if (value == 1) 不同,后者会因为类型转换而匹配。

因此,在使用 switch 时,要确保表达式和 case 值的类型一致:

javascript
let userInput = "42"; // 来自用户输入的字符串

// ❌ 不会匹配
switch (userInput) {
  case 42:
    console.log("Won't match");
    break;
}

// ✅ 先转换类型
switch (parseInt(userInput)) {
  case 42:
    console.log("Will match!");
    break;
}

// 或者匹配字符串
switch (userInput) {
  case "42":
    console.log("Will also match!");
    break;
}

块级作用域的注意事项

switch 语句中,整个 switch 块形成一个作用域。这意味着如果在不同的 case 中使用 letconst 声明同名变量,会产生错误:

javascript
let option = "a";

switch (option) {
  case "a":
    let message = "Option A selected"; // 声明变量
    console.log(message);
    break;
  case "b":
    let message = "Option B selected"; // ❌ 错误:重复声明
    console.log(message);
    break;
}

解决方法是为每个 case 创建独立的代码块:

javascript
let option = "a";

switch (option) {
  case "a": {
    let message = "Option A selected";
    console.log(message);
    break;
  }
  case "b": {
    let message = "Option B selected"; // ✅ 现在没问题了
    console.log(message);
    break;
  }
}

注意花括号的位置:它们在 casebreak 之间创建了一个新的块级作用域。或者,可以在 switch 之外声明变量,在 case 中赋值:

javascript
let option = "a";
let message; // 在 switch 外部声明

switch (option) {
  case "a":
    message = "Option A selected";
    console.log(message);
    break;
  case "b":
    message = "Option B selected";
    console.log(message);
    break;
}

实际应用场景

1. 处理 HTTP 状态码

javascript
function handleResponse(statusCode) {
  switch (statusCode) {
    case 200:
      console.log("Success: Request completed");
      return { success: true };
    case 201:
      console.log("Success: Resource created");
      return { success: true };
    case 400:
      console.log("Error: Bad request");
      return { success: false, error: "Invalid request" };
    case 401:
      console.log("Error: Unauthorized");
      return { success: false, error: "Authentication required" };
    case 404:
      console.log("Error: Not found");
      return { success: false, error: "Resource not found" };
    case 500:
      console.log("Error: Server error");
      return { success: false, error: "Internal server error" };
    default:
      console.log("Unknown status code:", statusCode);
      return { success: false, error: "Unknown error" };
  }
}

console.log(handleResponse(200)); // { success: true }
console.log(handleResponse(404)); // { success: false, error: "Resource not found" }

2. 游戏按键处理

javascript
function handleKeyPress(key) {
  switch (key) {
    case "w":
    case "ArrowUp":
      console.log("Move up");
      break;
    case "s":
    case "ArrowDown":
      console.log("Move down");
      break;
    case "a":
    case "ArrowLeft":
      console.log("Move left");
      break;
    case "d":
    case "ArrowRight":
      console.log("Move right");
      break;
    case " ":
      console.log("Jump");
      break;
    case "Escape":
      console.log("Pause game");
      break;
    default:
      console.log("Key not mapped");
  }
}

handleKeyPress("w"); // Move up
handleKeyPress("ArrowUp"); // Move up
handleKeyPress(" "); // Jump

3. 计算器操作

javascript
function calculate(num1, operator, num2) {
  let result;

  switch (operator) {
    case "+":
      result = num1 + num2;
      break;
    case "-":
      result = num1 - num2;
      break;
    case "*":
      result = num1 * num2;
      break;
    case "/":
      if (num2 === 0) {
        return "Error: Division by zero";
      }
      result = num1 / num2;
      break;
    case "%":
      result = num1 % num2;
      break;
    case "**":
      result = num1 ** num2;
      break;
    default:
      return "Error: Unknown operator";
  }

  return `${num1} ${operator} ${num2} = ${result}`;
}

console.log(calculate(10, "+", 5)); // 10 + 5 = 15
console.log(calculate(10, "/", 2)); // 10 / 2 = 5
console.log(calculate(10, "/", 0)); // Error: Division by zero
console.log(calculate(2, "**", 3)); // 2 ** 3 = 8

4. 状态机实现

javascript
let currentState = "idle";

function transition(action) {
  switch (currentState) {
    case "idle":
      switch (action) {
        case "start":
          currentState = "running";
          console.log("Started");
          break;
        default:
          console.log("Invalid action in idle state");
      }
      break;

    case "running":
      switch (action) {
        case "pause":
          currentState = "paused";
          console.log("Paused");
          break;
        case "stop":
          currentState = "idle";
          console.log("Stopped");
          break;
        default:
          console.log("Invalid action in running state");
      }
      break;

    case "paused":
      switch (action) {
        case "resume":
          currentState = "running";
          console.log("Resumed");
          break;
        case "stop":
          currentState = "idle";
          console.log("Stopped");
          break;
        default:
          console.log("Invalid action in paused state");
      }
      break;
  }

  console.log("Current state:", currentState);
}

transition("start"); // Started, Current state: running
transition("pause"); // Paused, Current state: paused
transition("resume"); // Resumed, Current state: running
transition("stop"); // Stopped, Current state: idle

Switch vs If-Else:如何选择

选择使用 switch 还是 if-else 取决于具体场景:

使用 Switch 的场景:

  • 基于单个变量的多个固定值进行分支
  • 分支数量较多(通常 3 个以上)
  • 每个分支的条件是简单的相等比较
  • 代码可读性和结构清晰度优先
javascript
// ✅ 适合用 switch
switch (statusCode) {
  case 200:
  // ...
  case 404:
  // ...
  case 500:
  // ...
}

使用 If-Else 的场景:

  • 需要复杂的条件判断(范围比较、逻辑运算符)
  • 基于不同变量的条件
  • 条件数量较少(1-3 个)
  • 需要灵活的条件表达式
javascript
// ✅ 适合用 if-else
if (age < 18) {
  // ...
} else if (age >= 18 && age < 65) {
  // ...
} else if (age >= 65 && hasDiscount) {
  // ...
}

// ❌ 不适合用 switch
switch (true) {
  case age < 18:
  // ...
  case age >= 18 && age < 65:
  // ...
}

有些开发者会用 switch (true) 来模拟 if-else,但这不是好的实践,降低了可读性。

常见陷阱与最佳实践

1. 忘记 Break

这是最常见的错误:

javascript
let score = 85;
let grade;

// ❌ 忘记 break
switch (true) {
  case score >= 90:
    grade = "A";
  case score >= 80:
    grade = "B"; // 会执行到这里
  case score >= 70:
    grade = "C"; // 还会执行到这里
  default:
    grade = "F"; // 最终 grade 是 "F"
}

console.log(grade); // F(错误!)

始终检查每个 case 后是否有 break,除非你明确想要 fall-through 行为。

2. 类型不匹配

javascript
let input = "2"; // 字符串

// ❌ 不会匹配
switch (input) {
  case 1:
    console.log("One");
    break;
  case 2:
    console.log("Two"); // 不会执行
    break;
}

// ✅ 确保类型一致
switch (parseInt(input)) {
  case 1:
    console.log("One");
    break;
  case 2:
    console.log("Two"); // 会执行
    break;
}

3. 使用对象替代长 Switch

switch 变得很长时,对象查找可能更清晰:

javascript
// ❌ 很长的 switch
function getAnimalSound(animal) {
  switch (animal) {
    case "dog":
      return "Woof!";
    case "cat":
      return "Meow!";
    case "cow":
      return "Moo!";
    case "duck":
      return "Quack!";
    // ... 更多动物
  }
}

// ✅ 使用对象
function getAnimalSound(animal) {
  const sounds = {
    dog: "Woof!",
    cat: "Meow!",
    cow: "Moo!",
    duck: "Quack!",
  };

  return sounds[animal] || "Unknown animal";
}

但如果每个 case 需要执行复杂的逻辑而不是简单的返回值,switch 可能仍然更合适。

4. 添加 Default 处理

即使认为已经覆盖了所有情况,也应该添加 default 来处理意外值:

javascript
function getDayType(day) {
  switch (day) {
    case "Monday":
    case "Tuesday":
    case "Wednesday":
    case "Thursday":
    case "Friday":
      return "Weekday";
    case "Saturday":
    case "Sunday":
      return "Weekend";
    default:
      // 处理无效输入
      console.error("Invalid day:", day);
      return "Invalid";
  }
}

console.log(getDayType("Moonday")); // Invalid (捕获了拼写错误)

总结

switch 语句是处理多分支选择的强大工具,特别适合基于单个变量的多个固定值进行判断的场景。它的结构清晰,可读性强,在合适的场景下比长长的 if-else 链更优雅。

关键要点:

  • switch 使用严格相等比较(===
  • 大多数 case 都应该以 break 结束
  • Fall-through 可以让多个 case 共享代码
  • 始终包含 default 子句处理意外情况
  • 当条件复杂或基于范围时,使用 if-else 更合适
  • 注意变量作用域问题,必要时使用花括号创建块级作用域
  • 当分支过多时,考虑使用对象查找或其他模式

掌握 switch 语句的正确用法,能够让你的代码在处理多分支逻辑时更加清晰和易维护。