Как работает оператор switch в JavaScript и как происходит сравнение?

👨‍💻 Frontend Developer 📊 Средний 🎚️ Легкий
#JavaScript #База JS

Краткий ответ

Оператор switch выполняет строгое сравнение (===) между выражением и значениями в case. Не приводит типы и использует fall-through механизм:

const value = "5";
 
switch (value) {
  case 5:        // false ("5" !== 5)
    console.log("Число");
    break;
  case "5":      // true ("5" === "5")
    console.log("Строка");
    break;
  default:
    console.log("Другое");
}
// Выведет: "Строка"

Ключевые особенности: Строгое сравнение, обязательный break, выполнение до первого break.


Как работает оператор switch

Базовый синтаксис

switch (выражение) {
  case значение1:
    // код для значения1
    break;
  case значение2:
    // код для значения2
    break;
  default:
    // код по умолчанию
}

Строгое сравнение (===)

const userInput = "1";
 
switch (userInput) {
  case 1:           // false ("1" !== 1)
    console.log("Число один");
    break;
  case "1":         // true ("1" === "1")
    console.log("Строка один");
    break;
  case true:        // false ("1" !== true)
    console.log("Булево");
    break;
}
// Выведет: "Строка один"

Сравнение с if-else

// Switch использует строгое сравнение
const value = 0;
 
switch (value) {
  case false:     // false (0 !== false)
    console.log("Ложь");
    break;
  case 0:         // true (0 === 0)
    console.log("Ноль");
    break;
}
// Выведет: "Ноль"
 
// Эквивалентный if-else
if (value === false) {
  console.log("Ложь");
} else if (value === 0) {
  console.log("Ноль");
}

Таблица сравнений switch vs if

Значениеswitch caseif (==)if (===)Объяснение
"5" vs 5falsetruefalseSwitch использует строгое сравнение
0 vs falsefalsetruefalseНет приведения типов в switch
"" vs 0falsetruefalseSwitch не приводит типы
null vs undefinedfalsetruefalseСтрогое сравнение в switch
1 vs truefalsetruefalseРазные типы для switch
"hello" vs "hello"truetruetrueОдинаковые строки
NaN vs NaNfalsefalsefalseNaN не равен самому себе

Механизм Fall-Through

Что такое Fall-Through

Fall-through — выполнение кода продолжается до первого break или конца блока:

const day = "понедельник";
 
switch (day) {
  case "понедельник":
    console.log("Начало недели");
    // НЕТ break! Продолжаем выполнение
  case "вторник":
    console.log("Рабочий день");
    break;
  case "суббота":
  case "воскресенье":
    console.log("Выходной");
    break;
}
 
// Выведет:
// "Начало недели"
// "Рабочий день"

Полезное использование Fall-Through

// Группировка случаев
function getSeasonByMonth(month) {
  switch (month) {
    case "декабрь":
    case "январь":
    case "февраль":
      return "Зима";
    
    case "март":
    case "апрель":
    case "май":
      return "Весна";
    
    case "июнь":
    case "июль":
    case "август":
      return "Лето";
    
    case "сентябрь":
    case "октябрь":
    case "ноябрь":
      return "Осень";
    
    default:
      return "Неизвестный месяц";
  }
}
 
console.log(getSeasonByMonth("январь")); // "Зима"

Опасности Fall-Through

⚠️ Внимание! Забытый break может привести к неожиданному поведению:

// ❌ Ошибка: забыли break
function processGrade(grade) {
  switch (grade) {
    case "A":
      console.log("Отлично!");
      // Забыли break!
    case "B":
      console.log("Хорошо!");
      break;
    case "C":
      console.log("Удовлетворительно");
      break;
    default:
      console.log("Нужно подтянуть");
  }
}
 
processGrade("A");
// Выведет:
// "Отлично!"
// "Хорошо!" (нежелательно!)

Практические примеры

1. Обработка HTTP статусов

function handleHttpStatus(status) {
  switch (status) {
    case 200:
    case 201:
    case 204:
      return "Успех";
    
    case 400:
      return "Неверный запрос";
    
    case 401:
      return "Не авторизован";
    
    case 403:
      return "Доступ запрещен";
    
    case 404:
      return "Не найдено";
    
    case 500:
    case 502:
    case 503:
      return "Ошибка сервера";
    
    default:
      return `Неизвестный статус: ${status}`;
  }
}
 
console.log(handleHttpStatus(200));  // "Успех"
console.log(handleHttpStatus("200")); // "Неизвестный статус: 200"

2. Калькулятор

function calculate(a, operator, b) {
  switch (operator) {
    case "+":
      return a + b;
    
    case "-":
      return a - b;
    
    case "*":
      return a * b;
    
    case "/":
      if (b === 0) {
        throw new Error("Деление на ноль");
      }
      return a / b;
    
    case "**":
    case "^":
      return Math.pow(a, b);
    
    default:
      throw new Error(`Неизвестный оператор: ${operator}`);
  }
}
 
console.log(calculate(5, "+", 3));  // 8
console.log(calculate(10, "/", 2)); // 5

3. Валидация типов

function validateInput(value, expectedType) {
  const actualType = typeof value;
  
  switch (expectedType) {
    case "string":
      if (actualType === "string" && value.length > 0) {
        return { valid: true, message: "Валидная строка" };
      }
      return { valid: false, message: "Ожидается непустая строка" };
    
    case "number":
      if (actualType === "number" && !isNaN(value)) {
        return { valid: true, message: "Валидное число" };
      }
      return { valid: false, message: "Ожидается число" };
    
    case "boolean":
      if (actualType === "boolean") {
        return { valid: true, message: "Валидный boolean" };
      }
      return { valid: false, message: "Ожидается boolean" };
    
    case "array":
      if (Array.isArray(value)) {
        return { valid: true, message: "Валидный массив" };
      }
      return { valid: false, message: "Ожидается массив" };
    
    default:
      return { valid: false, message: `Неизвестный тип: ${expectedType}` };
  }
}
 
console.log(validateInput("hello", "string")); // { valid: true, ... }
console.log(validateInput(42, "string"));      // { valid: false, ... }

Практические задачи

Задача 1: Что выведет консоль?

const value = 1;
 
switch (value) {
  case "1":
    console.log("Строка");
    break;
  case 1:
    console.log("Число");
  case true:
    console.log("Булево");
    break;
  default:
    console.log("Другое");
}
Ответ

Объяснение:

  • value = 1 строго равно case 1, поэтому выполняется этот блок
  • Нет break после case 1, поэтому выполнение продолжается
  • Выполняется код в case true (fall-through)
  • break останавливает выполнение

Задача 2: Найдите ошибку

function getDayType(day) {
  switch (day) {
    case "понедельник":
    case "вторник":
    case "среда":
    case "четверг":
    case "пятница":
      return "Рабочий день";
    case "суббота":
    case "воскресенье":
      return "Выходной";
  }
}
 
console.log(getDayType("понедельник")); // ?
console.log(getDayType("ПОНЕДЕЛЬНИК")); // ?
console.log(getDayType("пн"));          // ?
Ответ

Результат:

  • getDayType("понедельник")"Рабочий день"
  • getDayType("ПОНЕДЕЛЬНИК")undefined
  • getDayType("пн")undefined

Проблемы:

  1. Нет default случая
  2. Регистрозависимость
  3. Не обрабатывает сокращения

Исправленная версия:

function getDayType(day) {
  const normalizedDay = day.toLowerCase();
  
  switch (normalizedDay) {
    case "понедельник":
    case "пн":
    case "вторник":
    case "вт":
    case "среда":
    case "ср":
    case "четверг":
    case "чт":
    case "пятница":
    case "пт":
      return "Рабочий день";
    
    case "суббота":
    case "сб":
    case "воскресенье":
    case "вс":
      return "Выходной";
    
    default:
      return "Неизвестный день";
  }
}

Задача 3: Что выведет код?

const score = "85";
 
switch (true) {
  case score >= 90:
    console.log("A");
    break;
  case score >= 80:
    console.log("B");
    break;
  case score >= 70:
    console.log("C");
    break;
  default:
    console.log("F");
}
Ответ

Объяснение:

  • score = "85" (строка)
  • "85" >= 90false
  • "85" >= 80true, но true !== true в контексте switch
  • Все сравнения возвращают boolean, но switch ищет точное совпадение с true
  • Ни один case не выполняется, срабатывает default

Правильный способ:

const score = Number("85"); // Приводим к числу
 
if (score >= 90) {
  console.log("A");
} else if (score >= 80) {
  console.log("B");
} else if (score >= 70) {
  console.log("C");
} else {
  console.log("F");
}

Задача 4: Исправьте функцию

// ❌ Проблемная функция
function getDiscountByCategory(category, isPremium) {
  switch (category) {
    case "electronics":
      if (isPremium) {
        return 0.15;
      }
      return 0.10;
    case "clothing":
      if (isPremium) {
        return 0.20;
      }
      return 0.15;
    case "books":
      return 0.05;
  }
}
 
console.log(getDiscountByCategory("electronics", true));  // ?
console.log(getDiscountByCategory("unknown", false));     // ?
Ответ

Проблемы:

  1. Нет default случая
  2. Функция может вернуть undefined
  3. Нет обработки некорректных входных данных

Исправленная версия:

function getDiscountByCategory(category, isPremium = false) {
  // Валидация входных данных
  if (typeof category !== "string") {
    throw new Error("Категория должна быть строкой");
  }
  
  if (typeof isPremium !== "boolean") {
    throw new Error("isPremium должен быть boolean");
  }
  
  const normalizedCategory = category.toLowerCase();
  
  switch (normalizedCategory) {
    case "electronics":
      return isPremium ? 0.15 : 0.10;
    
    case "clothing":
      return isPremium ? 0.20 : 0.15;
    
    case "books":
      return 0.05; // Одинаковая скидка для всех
    
    default:
      return 0; // Нет скидки для неизвестных категорий
  }
}
 
// Альтернативный подход с объектом
const DISCOUNTS = {
  electronics: { regular: 0.10, premium: 0.15 },
  clothing: { regular: 0.15, premium: 0.20 },
  books: { regular: 0.05, premium: 0.05 }
};
 
function getDiscountByCategory2(category, isPremium = false) {
  const categoryData = DISCOUNTS[category.toLowerCase()];
  if (!categoryData) {
    return 0;
  }
  return isPremium ? categoryData.premium : categoryData.regular;
}

Когда использовать switch vs if-else

Используйте switch когда:

// ✅ Много конкретных значений
function getHttpStatusText(code) {
  switch (code) {
    case 200: return "OK";
    case 201: return "Created";
    case 400: return "Bad Request";
    case 401: return "Unauthorized";
    case 403: return "Forbidden";
    case 404: return "Not Found";
    case 500: return "Internal Server Error";
    default: return "Unknown Status";
  }
}
 
// ✅ Группировка похожих случаев
function isWeekend(day) {
  switch (day.toLowerCase()) {
    case "saturday":
    case "sunday":
      return true;
    default:
      return false;
  }
}
 
// ✅ Конечный автомат (state machine)
function processState(currentState, action) {
  switch (currentState) {
    case "idle":
      switch (action) {
        case "start": return "running";
        case "reset": return "idle";
        default: return currentState;
      }
    case "running":
      switch (action) {
        case "pause": return "paused";
        case "stop": return "stopped";
        default: return currentState;
      }
    // ... другие состояния
  }
}

Используйте if-else когда:

// ✅ Диапазоны значений
function getGrade(score) {
  if (score >= 90) {
    return "A";
  } else if (score >= 80) {
    return "B";
  } else if (score >= 70) {
    return "C";
  } else if (score >= 60) {
    return "D";
  } else {
    return "F";
  }
}
 
// ✅ Сложные условия
function canAccessResource(user, resource) {
  if (user.isAdmin) {
    return true;
  } else if (user.role === "moderator" && resource.type === "public") {
    return true;
  } else if (user.id === resource.ownerId) {
    return true;
  } else {
    return false;
  }
}
 
// ✅ Условия с логическими операторами
function shouldShowNotification(user, notification) {
  if (user.preferences.notifications && 
      !user.isDoNotDisturb && 
      notification.priority === "high") {
    return true;
  }
  return false;
}

Современные альтернативы

1. Объект как lookup таблица

// Вместо switch
function getAnimalSound(animal) {
  switch (animal) {
    case "dog": return "Woof!";
    case "cat": return "Meow!";
    case "cow": return "Moo!";
    case "pig": return "Oink!";
    default: return "Unknown sound";
  }
}
 
// ✅ Современный подход
const ANIMAL_SOUNDS = {
  dog: "Woof!",
  cat: "Meow!",
  cow: "Moo!",
  pig: "Oink!"
};
 
function getAnimalSound(animal) {
  return ANIMAL_SOUNDS[animal] ?? "Unknown sound";
}

2. Map для сложных случаев

// ✅ Map с функциями
const ACTION_HANDLERS = new Map([
  ["CREATE", (data) => createItem(data)],
  ["UPDATE", (data) => updateItem(data)],
  ["DELETE", (data) => deleteItem(data)],
  ["READ", (data) => readItem(data)]
]);
 
function handleAction(action, data) {
  const handler = ACTION_HANDLERS.get(action.toUpperCase());
  if (handler) {
    return handler(data);
  }
  throw new Error(`Unknown action: ${action}`);
}

3. Полиморфизм (классы)

// ✅ Объектно-ориентированный подход
class Shape {
  calculateArea() {
    throw new Error("Method must be implemented");
  }
}
 
class Circle extends Shape {
  constructor(radius) {
    super();
    this.radius = radius;
  }
  
  calculateArea() {
    return Math.PI * this.radius ** 2;
  }
}
 
class Rectangle extends Shape {
  constructor(width, height) {
    super();
    this.width = width;
    this.height = height;
  }
  
  calculateArea() {
    return this.width * this.height;
  }
}
 
// Использование
function getArea(shape) {
  return shape.calculateArea(); // Полиморфизм вместо switch
}

Лучшие практики

1. Всегда используйте break

// ✅ Хорошо: явный break
switch (action) {
  case "save":
    saveData();
    break;
  case "load":
    loadData();
    break;
  default:
    showError();
    break; // Даже в default для консистентности
}
 
// ❌ Плохо: забытый break
switch (action) {
  case "save":
    saveData();
    // Забыли break!
  case "load":
    loadData(); // Выполнится и для "save"!
    break;
}

2. Используйте default

// ✅ Хорошо: обрабатываем все случаи
function processUserRole(role) {
  switch (role) {
    case "admin":
      return { permissions: ["read", "write", "delete"] };
    case "user":
      return { permissions: ["read"] };
    case "guest":
      return { permissions: [] };
    default:
      throw new Error(`Unknown role: ${role}`);
  }
}
 
// ❌ Плохо: нет default
function processUserRoleBad(role) {
  switch (role) {
    case "admin":
      return { permissions: ["read", "write", "delete"] };
    case "user":
      return { permissions: ["read"] };
  }
  // Что если role = "guest"? Вернется undefined!
}

3. Группируйте связанные случаи

// ✅ Хорошо: логическая группировка
function getBusinessHours(day) {
  switch (day.toLowerCase()) {
    case "monday":
    case "tuesday":
    case "wednesday":
    case "thursday":
    case "friday":
      return "9:00 - 18:00";
    
    case "saturday":
      return "10:00 - 16:00";
    
    case "sunday":
      return "Закрыто";
    
    default:
      return "Неизвестный день";
  }
}

4. Избегайте сложной логики в case

// ❌ Плохо: сложная логика в case
switch (userType) {
  case "premium":
    if (user.subscriptionActive && user.paymentValid) {
      if (user.lastLogin > Date.now() - 30 * 24 * 60 * 60 * 1000) {
        return getPremiumFeatures();
      } else {
        return getBasicFeatures();
      }
    }
    break;
  // ...
}
 
// ✅ Хорошо: выносим логику в функции
function isPremiumActive(user) {
  return user.subscriptionActive && 
         user.paymentValid && 
         user.lastLogin > Date.now() - 30 * 24 * 60 * 60 * 1000;
}
 
switch (userType) {
  case "premium":
    return isPremiumActive(user) ? getPremiumFeatures() : getBasicFeatures();
  case "basic":
    return getBasicFeatures();
  default:
    return getGuestFeatures();
}

5. Используйте ESLint правила

// .eslintrc.json
{
  "rules": {
    "default-case": "error",           // Требует default
    "no-fallthrough": "error",        // Предупреждает о fall-through
    "no-case-declarations": "error"   // Запрещает объявления в case
  }
}

Частые ошибки

1. Забытый break

// ❌ Ошибка
function getPrice(category) {
  let price = 0;
  
  switch (category) {
    case "basic":
      price = 10;
      // Забыли break!
    case "premium":
      price = 20;
      break;
    case "enterprise":
      price = 50;
      break;
  }
  
  return price;
}
 
console.log(getPrice("basic")); // 20 вместо 10!

2. Объявление переменных в case

// ❌ Ошибка: переменные "всплывают"
switch (type) {
  case "A":
    let result = "Type A"; // Ошибка!
    break;
  case "B":
    let result = "Type B"; // Ошибка: повторное объявление!
    break;
}
 
// ✅ Правильно: используйте блоки
switch (type) {
  case "A": {
    let result = "Type A";
    console.log(result);
    break;
  }
  case "B": {
    let result = "Type B";
    console.log(result);
    break;
  }
}

3. Сравнение с приведением типов

// ❌ Ошибка: ожидание приведения типов
const userInput = "1";
 
switch (userInput) {
  case 1:  // Не сработает! "1" !== 1
    console.log("Один");
    break;
}
 
// ✅ Правильно: приводим тип заранее
const userInputNumber = Number(userInput);
 
switch (userInputNumber) {
  case 1:
    console.log("Один");
    break;
}

4. Использование switch для диапазонов

// ❌ Плохо: switch не подходит для диапазонов
switch (true) {
  case age >= 18 && age < 65:
    return "Взрослый";
  case age >= 65:
    return "Пенсионер";
  default:
    return "Ребенок";
}
 
// ✅ Хорошо: используйте if-else
if (age >= 65) {
  return "Пенсионер";
} else if (age >= 18) {
  return "Взрослый";
} else {
  return "Ребенок";
}

Производительность

Switch vs If-Else

// Для многих условий switch может быть быстрее
function getColorHex(color) {
  // Switch: O(1) в лучшем случае (jump table)
  switch (color) {
    case "red": return "#FF0000";
    case "green": return "#00FF00";
    case "blue": return "#0000FF";
    case "yellow": return "#FFFF00";
    case "purple": return "#800080";
    case "orange": return "#FFA500";
    case "pink": return "#FFC0CB";
    case "brown": return "#A52A2A";
    default: return "#000000";
  }
}
 
// If-else: O(n) в худшем случае
function getColorHexIfElse(color) {
  if (color === "red") return "#FF0000";
  else if (color === "green") return "#00FF00";
  else if (color === "blue") return "#0000FF";
  else if (color === "yellow") return "#FFFF00";
  else if (color === "purple") return "#800080";
  else if (color === "orange") return "#FFA500";
  else if (color === "pink") return "#FFC0CB";
  else if (color === "brown") return "#A52A2A";
  else return "#000000";
}
 
// Объект: O(1) всегда
const COLOR_MAP = {
  red: "#FF0000",
  green: "#00FF00",
  blue: "#0000FF",
  yellow: "#FFFF00",
  purple: "#800080",
  orange: "#FFA500",
  pink: "#FFC0CB",
  brown: "#A52A2A"
};
 
function getColorHexObject(color) {
  return COLOR_MAP[color] ?? "#000000";
}

Заключение

Ключевые моменты о switch:

  1. Строгое сравнение — использует ===, не приводит типы
  2. Fall-through — выполнение продолжается до break
  3. Обязательный break — без него код “проваливается” в следующий case
  4. Default случай — всегда добавляйте для обработки неожиданных значений
  5. Группировка — используйте для объединения похожих случаев

Когда использовать:

  • ✅ Много конкретных значений для сравнения
  • ✅ Группировка похожих случаев
  • ✅ Конечные автоматы (state machines)
  • ❌ Диапазоны значений (используйте if-else)
  • ❌ Сложные условия (используйте if-else)

Современные альтернативы:

  • Объекты как lookup таблицы
  • Map для сложных случаев
  • Полиморфизм для ООП подхода

Помните: Switch — мощный инструмент, но не универсальное решение. Выбирайте подходящий инструмент для каждой задачи!


Хотите больше статей для подготовки к собеседованиям? Подписывайтесь на EasyAdvice(@AleksandrEmolov_EasyAdvice), добавляйте сайт в закладки и прокачивайтесь каждый день 💪