【JavaScript】10个技巧干掉你代码中那些丑陋冗长的 if…else 语句~

【JavaScript】10个技巧干掉你代码中那些丑陋冗长的 if...else 语句~

1、三元运算符

在实际工作中,我们常常需要对一个变量或表达式进行判断,根据不同的条件来执行不同的操作,通常情况下需要使用 if...else 语句。但是 if...else 语句过于冗长和繁琐,可以使用三元运算符来优化。

以实现一个状态机的代码为例,原始的代码可能长这样:

function handleEvent(event) {
  if (currentState === STATE_INITIAL) {
    // do something
  } else if (currentState === STATE_SECONDARY) {
    // do something
  } else if (currentState === STATE_FINAL) {
    // do something
  }
}

三元运算符可以用于精简此类代码:

function handleEvent(event) {
  currentState === STATE_INITIAL ? 
        // do something :
    currentState === STATE_SECONDARY ?
        // do something :
        // do something
}

使用三元运算符可以使得代码更加简洁易懂,并且减少代码文件大小。同时,三元运算符可以提高代码可读性,逻辑也可以一眼看出来,从而更快速地找到问题所在和进行调试。在某些特定场景下,使用三元运算符还可以提高性能,因为有些引擎不能很好地理解 if...else 语句的含义,从而会降低其性能表现。

注意:虽然使用三元运算符来优化 if...else 语句能够大大提高代码的可读性、可维护性和性能表现,值得我们在实际工作中加以运用。不过需要注意的是,如果一个条件比较复杂或者拥有多个分支时,使用三元运算符会让代码难以阅读和理解,切忌不要滥用。

2、耍点小聪明

来看两个特定场景下的”小聪明技巧“~

2.1 短路求值(Short-circuit Evaluation)

短路求值是指在逻辑运算中,如果第一个表达式已经能够确定整个表达式的值,就不再计算后面的表达式。利用这个特性,可以通过 && 和 || 运算符来缩短 if...else 语句。例如:

let x = 10;
let result;
if (x > 5) {
  result = "x is greater than 5";
} else {
  result = "x is less than or equal to 5";
}

// 利用 && 运算符
let x = 10;
let result = x > 5 && "x is greater than 5" || "x is less than or equal to 5";

2.2 字符串拼接

先来看一类比较特殊的 if...else 语句:

function setAccType(accType) {
    if (accType == "PLATINUM") {
        return "Platinum Customer";
    } else if (accType == "GOLD") {
        return "Gold Customer";
    } else if (accType == "SILVER") {
        return "Silver Customer";
    }
}

像这种条件和返回值有非常强的相关性的语句,通常我们就可以通过下面这种非常简洁的方式来处理,让多行秒变一行:

function setAccType(accType){
    return accType[0] + accType.slice(1).toLowerCase() + ' Customer';
    // or
    return `${accType[0] + accType.slice(1).toLowerCase()} Customer`;
}

3、switch/case

switch/case 语句也是比较常用的技巧,来看下面这个例子:

if (status === 200) {
  handleSuccess()
} else if (status === 401) {
  handleUnauthorized()
} else if (status === 404) {
  handleNotFound()
} else {
  handleUnknownError()
}

像这样的,我们就可以通过 switch/case 来进行处理:

switch (status) {  
    case 200:  
        handleSuccess()  
        break  
    case 401:  
        handleUnauthorized()  
        break  
    case 404:  
        handleNotFound()  
        break  
    default:  
        handleUnknownError()  
        break  
}

虽然多了几行代码,但避免了一次又一次的重复的全等检查,而且整体上更精简、直观。

4、数组的 includes 方法

假设我们要根据所提供的食品类型返回相应的配料数组。如果使用if..else语句,通常会这样写:

function getIngredients(foodType) {
  let ingredients = [];

  if (foodType === 'pizza') {
    ingredients = ['cheese', 'sauce', 'pepperoni'];
  } else if (foodType === 'burger') {
    ingredients = ['bun', 'beef patty', 'lettuce', 'tomato', 'onion'];
  } else if (foodType === 'taco') {
    ingredients = ['tortilla', 'beef', 'lettuce', 'shredded cheese'];
  } else if (foodType === 'sandwich') {
    ingredients = ['bread', 'turkey', 'lettuce', 'mayo'];
  } else {
    console.log('Unknown food type');
  }

  return ingredients;
}

这样的场景下,就可以使用includes方法来优化这个冗长的if...else语句:

function getIngredients(foodType) {
  const menu = [
    {food: 'pizza', ingredients: ['cheese', 'sauce', 'pepperoni']},
    {food: 'burger', ingredients: ['bun', 'beef patty', 'lettuce', 'tomato', 'onion']},
    {food: 'taco', ingredients: ['tortilla', 'beef', 'lettuce', 'shredded cheese']},
    {food: 'sandwich', ingredients: ['bread', 'turkey', 'lettuce', 'mayo']}
  ];
  
  const item = menu.find(menuItem => menuItem.food === foodType);
  
  if (item === undefined) {
    console.log('Unknown food type');
    return [];
  }
  
  return item.ingredients;
}

这样做的好处在于:

  1. 可读性更强:使用数组和对象结合来代替if..else语句,代码更加简洁易懂,并且减少了嵌套,更容易阅读和理解。
  2. 扩展性更好:添加新的菜单项只需要向menu数组中添加一个新的对象即可,而不需要修改原函数getIngredients,大大提高了代码的扩展性。
  3. 可维护性更好:当想要删除或更新某个菜单项时,只需要修改menu数组即可,而不需要修改原函数getIngredients。因此,代码的可维护性也更好。

5、使用对象或者Map

5.1 对象

比如我们通常会遇到这种场景:

function getStatusColor (status) {  
    if (status === 'success') {  
        return 'green'  
    }  
    
    if (status === 'warning') {  
        return 'yellow'  
    }  
    
    if (status === 'info') {  
        return 'blue'  
    }  
    
    if (status === 'error') {  
        return 'red'  
    }  
}
复制代码
const statusColors = {
    success: 'green',
    warning: 'yellow',
    info: 'blue',
    error: 'red'
};

function getStatusColor(status) {
    return statusColors[status] || '';
}

这样做的好处有两个:

  1. 可读性更好:当需要增加新的状态时,只需要添加一个新的键值对即可。这比原来的if..else语句更易于阅读和维护。
  2. 执行效率更高:因为在JavaScript中,对象的属性访问是常数时间,而if..else语句则需要逐个匹配条件。由于我们只需要访问一个属性,所以使用对象映射比if..else语句更有效率。

5.2 Map

来看下面的例子,我们要根据颜色打印水果:

function test(color) {
  switch (color) {
    case 'red':
      return ['apple', 'strawberry'];
    case 'yellow':
      return ['banana', 'pineapple'];
    case 'purple':
      return ['grape', 'plum'];
    default:
      return [];
  }
}

test(null); // []
test('yellow'); // ['banana', 'pineapple']
复制代码

我们可以使用Map数据结构来优化上面的代码,具体实现如下:

const foodMap = new Map([
    ['red', ['apple', 'strawberry']],
    ['yellow', ['banana', 'pineapple']],
    ['purple', ['grape', 'plum']]
  ]);
  
function test(color) {
  return foodMap.get(color) || [];
}

这样做的好处在于:

  1. 这段代码更加简洁:使用Map数据结构可以使代码更短,可读性也更强。
  2. 可维护性更高:将数据存储在Map中,如果需要修改或添加一个颜色和对应的食物列表,只需在Map中修改或添加一条记录即可。
  3. 搜索时间更短:由于Map内部使用了哈希表来存储键值对,所以搜索时间可能比基于条件语句的方法更快。当有大量数据时,这种优化可能会显著提高代码的性能。

6、减少嵌套,提前返回

6.1 场景1

假设我们需要根据用户的年龄,返回一个字符串表示他们的人生阶段。使用if..else语句,一般可能会这样写:

function getLifeStage(age) {
  let stage;

  if (age < 1) {
    stage = 'baby';
  } else {
    if (age < 4) {
      stage = 'toddler';
    } else {
      if (age < 13) {
        stage = 'child';
      } else {
        if (age < 20) {
          stage = 'teenager';
        } else {
          if (age < 65) {
            stage = 'adult';
          } else {
            stage = 'senior';
          }
        }
      }
    }
  }

  return `Your life stage is ${stage}`;
}

我们可以使用减少嵌套和提前返回的方法来优化这个冗长的if...else语句,代码如下所示:

function getLifeStage(age) {
  if (age < 1) {
    return 'Your life stage is baby';
  }
  
  if (age < 4) {
    return 'Your life stage is toddler';
  }
  
  if (age < 13) {
    return 'Your life stage is child';
  }
  
  if (age < 20) {
    return 'Your life stage is teenager';
  }
  
  if (age < 65) {
    return 'Your life stage is adult';
  }
  
  return 'Your life stage is senior';
}

这样做的好处在于:

  1. 可读性更强:减少了嵌套层次以及大括号,使代码更加简洁明了,并且易于阅读和理解。
  2. 扩展性更好:当需要添加或删除一个年龄段时,只需要在相应的位置插入或删除一个if语句即可,而不需要影响其他部分的代码,大大提高了代码的扩展性。
  3. 错误处理更好:每个if语句可以处理一种情况,这样可以更及时地返回正确的结果。同时,避免了多个判断条件匹配的情况下重复执行同一个代码块的情况。

6.2 场景2

来看看下面这个例子:

function test(fruit, quantity) {
  const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries'];

  // 条件1: fruit 必须非空
  if (fruit) {
    // 条件2: 必须是红色的水果
    if (redFruits.includes(fruit)) {
      console.log('red');

      // 条件3: 必须大于10个
      if (quantity > 10) {
        console.log('big quantity');
      }
    }
  } else {
    throw new Error('No fruit!');
  }
}

test(null); // error: No fruits
test('apple'); // red
test('apple', 20); // red, big quantity

看看上面的代码,我们有:

  • 1个if/else语句,过滤掉无效的条件
  • 3层嵌套的if语句(条件1、2和3)。

我个人遵循的一般规则是在将一些比较容易处理、计算量小、小概率的提前返回。

function test(fruit, quantity) {
  const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries'];

  // 先判断小概率情况,提前返回
  if (!fruit) throw new Error('No fruit!');

  if (redFruits.includes(fruit)) {
    console.log('red');

    if (quantity > 10) {
      console.log('big quantity');
    }
  }
}

通过这样做,我们就少了一层嵌套语句。这种编码方式很好,特别是当你有很长的if语句时(想象一下,你需要滚动到最下面才知道有一个else语句,这可读性可能不会太好)。我们可以进一步减少嵌套的if,通过倒置条件和提前返回。比如:

function test(fruit, quantity) {
  const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries'];

  if (!fruit) throw new Error('No fruit!'); 
  if (!redFruits.includes(fruit)) return; 

  console.log('red');

  if (quantity > 10) {
    console.log('big quantity');
  }
}

通过颠倒条件2的条件,我们的代码现在已经没有了嵌套的语句,代码变得很简洁直观。

7、隐藏高阶技能:find(Boolean)

写 react 的同学可能在一些组件复用的场景会经常遇到这种情况:组件中有少部分组件会根据传入的props属性不同进行展示。

function ComponentByType({ type }) {
    if (type === "type_1") {
        return (
            <>
                {/* 其他很复杂的组件 */}
                <Component1 />
                {/* 其他很复杂的组件 */}
            </>
        );
    }

    if (type === "type_2") {
        return (
            <>
                {/* 其他很复杂的组件 */}
                <Component2 />
                {/* 其他很复杂的组件 */}
            </>
        );
    }

    if (type === "type_3") {
        return (
            <>
                {/* 其他很复杂的组件 */}
                <Component3 />
                {/* 其他很复杂的组件 */}
            </>
        );
    }

    if (type === "type_4") {
        return (
            <>
                {/* 其他很复杂的组件 */}
                <Component4 />
                {/* 其他很复杂的组件 */}
            </>
        );
    }
}

而这种情况,可以通过find(Boolean)这个小技巧来获得极大的简化:

function ComponentByType({ type }) {
    return (
        <>
            {/* 其他很复杂的组件 */}
            <>
                {
                    [
                        type === "type_1" && <Component1 />,
                        type === "type_2" && <Component2 />,
                        type === "type_3" && <Component3 />,
                        type === "type_4" && <Component4 />
                    ].find(Boolean)
                }
            </>
            {/* 其他很复杂的组件 */}
        </>
    );
}

find(Boolean)可以实现条件判断,因为在JavaScript中,布尔值的true和false可以被转换为数字1和0。在这个例子里,当某个条件不满足时,对应的React组件(例如<Component1 />)会被解析为布尔值false并存储在数组中,相反当条件满足时,对应的React组件会被解析为一个真值true。这样,使用find(Boolean)就可以找到第一个真值(即第一个满足条件的React组件),并把它渲染到页面上。

需要注意的是,当没有任何一个条件满足时,.find()方法返回的是undefined,会导致React渲染错误。但由于在这个例子中我们把结果用短路语法忽略了,因此不会有报错。

如果希望更准确地处理没有满足条件的情况,可以显式地在.find()后面添加一个默认返回值,在找不到满足条件的组件时返回默认组件。例如:

[
    type === "type_1" && <Component1 />,
    type === "type_2" && <Component2 />,
    type === "type_3" && <Component3 />,
    type === "type_4" && <Component4 />
].find(Boolean) || <DefaultComponent />

这样,当四种类型都不匹配时,就会默认渲染 <DefaultComponent> 组件,避免了React渲染错误。通过使用find(Boolean),可以简化代码,避免了使用多个if/else语句或switch/case语句的复杂性和冗长性。

原理是什么呢?

find(Boolean)的作用是查找数组中第一个布尔值为true的元素,并返回该元素的值。数组中包含多个条件语句以及相应的React组件,当某个条件满足时,对应的组件会被返回并渲染到页面上;否则会返回false,由于我们使用了条件运算符&&,所以这个返回值会被自动忽略。

比如一个简单的例子:

[0,false,null,1,2].find(Boolean) // 返回 1

在不加 new 的情况下调用 Boolean 函数会将传入的参数转换为布尔值。对于数组中的各个元素,利用 Boolean 函数进行类型转换后的结果如下:

  • 0 被转换为 false
  • false 本身就是布尔值 false
  • null 被转换为 false
  • 1 被转换为 true
  • 2 被转换为 true

因此,使用 find 方法查找第一个真值时,返回了第一个布尔值为 true 的元素的索引。在这个数组中,第一个布尔值为 true 的元素是 1,它的索引是 3,因此 find 方法返回了 1。需要注意的是,如果该数组中不存在任何布尔值为 true 的元素,则 find 方法返回 undefined。

[0,false,null,1,2].map(Boolean); // [false, false, false, true, true]

[0,false,null,1,2].find(Boolean); // 1

其中,map 方法将数组中的每个元素都应用一次 Boolean 函数,得到一个新的布尔类型的数组。可以看到,在这个新数组中,第一个布尔值为 true 的元素是 true,它的索引是 3。因此,find 方法在原始数组中查找第一个布尔值为 true 的元素时,返回了 1。

8、责任链模式

责任链模式(Chain of Responsibility Pattern)是一种行为型设计模式,它可以使多个对象都有机会处理请求。在 JavaScript 中,我们可以利用责任链模式来优化复杂、冗长的 if...else 语句。

在责任链模式中,各个处理者构成了一个链条,当有请求发生时,这些处理者按照顺序依次尝试处理请求,直到有处理者成功处理请求或所有处理者尝试完毕为止。因此,责任链模式可以动态地组织和修改处理流程,从而提高代码的灵活性。

在实际工作中,有很多场景可以使用责任链模式来优化复杂、冗长的 if...else 语句。

比如,在一个电商平台上,用户下单后需要进行一系列的校验和处理,比如校验商品库存是否充足、校验收货地址是否正确等。而这些校验和处理操作可能会有多个步骤,如果采用传统的 if...else 语句来实现,代码很容易变得冗长难懂。下面是一个简单的示例代码:

function placeOrder(order) {
  if (!order.items || !Array.isArray(order.items) || order.items.length === 0) {
    console.log("At least one item should be specified to place an order.");
    return;
  }

  // 校验商品库存是否充足
  for (let i = 0; i < order.items.length; i++) {
    let item = order.items[i];
    if (!checkStock(item.productId, item.quantity)) {
      console.log(`Product ${item.productId} is out of stock.`);
      return;
    }
  }

  if (!order.address || !isValidAddress(order.address)) {
    console.log("Invalid address.");
    return;
  }

  // 扣减库存
  for (let i = 0; i < order.items.length; i++) {
    let item = order.items[i];
    decreaseStock(item.productId, item.quantity);
  }

  // 生成订单
  let orderId = generateOrderId();
  console.log(`Order ${orderId} placed successfully.`);
}

在上述代码中,我们使用了多个 if...else 语句来完成订单校验和处理操作。这样的代码难以维护和扩展,而且不符合开闭原则(对扩展开放,对修改关闭)。

使用责任链模式可以有效解决这个问题。下面是一个使用责任链模式来实现订单处理的示例代码:

class OrderValidator {
  constructor() {
    this.nextHandler = null;
  }
  setNext(handler) {
    this.nextHandler = handler;
    return handler;
  }
  handle(order) {}
}

class ItemValidator extends OrderValidator {
  handle(order) {
    if (!order.items || !Array.isArray(order.items) || order.items.length === 0) {
      console.log("At least one item should be specified to place an order.");
      return false;
    } else {
      return this.nextHandler.handle(order);
    }
  }
}

class StockValidator extends OrderValidator {
  handle(order) {
    for (let i = 0; i < order.items.length; i++) {
      let item = order.items[i];
      if (!checkStock(item.productId, item.quantity)) {
        console.log(`Product ${item.productId} is out of stock.`);
        return false;
      }
    }
    return this.nextHandler.handle(order);
  }
}

class AddressValidator extends OrderValidator {
  handle(order) {
    if (!order.address || !isValidAddress(order.address)) {
      console.log("Invalid address.");
      return false;
    } else {
      return this.nextHandler.handle(order);
    }
  }
}

class StockDeductor extends OrderValidator {
  handle(order) {
    for (let i = 0; i < order.items.length; i++) {
      let item = order.items[i];
      decreaseStock(item.productId, item.quantity);
    }
    return this.nextHandler.handle(order);
  }
}

class OrderGenerator extends OrderValidator {
  handle(order) {
    let orderId = generateOrderId();
    console.log(`Order ${orderId} placed successfully.`);
    return true;
  }
}

let itemValidator = new ItemValidator();
let stockValidator = new StockValidator();
let addressValidator = new AddressValidator();
let stockDeductor = new StockDeductor();
let orderGenerator = new OrderGenerator();

itemValidator.setNext(stockValidator).setNext(addressValidator).setNext(stockDeductor).setNext(orderGenerator);

function placeOrder(order) {
  itemValidator.handle(order);
}

在上述代码中,我们定义了多个订单处理器(比如 ItemValidatorStockValidator 等),它们继承自 OrderValidator 类。这些处理器构成了一个处理链,并通过 setNext() 方法连接起来。当用户下单后,我们只需要调用 placeOrder() 函数并将订单对象作为参数传入即可。

在处理过程中,每个处理器都会检查订单是否满足其要求。如果满足就继续交给下一个处理器去处理,否则直接返回。如果所有处理器都能够成功处理订单,最终会输出 “Order x placed successfully.” 的提示信息。

使用责任链模式的好处:

  1. 代码更加灵活和易于扩展:在需要添加或修改处理步骤时,只需要新增或修改相应的处理器类即可,不需要修改原有的代码。
  2. 代码复用性更高:不同的处理器类可以被复用,具有更好的可读性和可维护性。
  3. 代码结构更加清晰和易于理解:使用责任链模式将系统分解成多个小而简单的部分,每一个部分都有自己的处理逻辑和职责。这样的代码结构更加清晰和易于理解。

作者:你当像鸟飞往你的山

消息盒子
# 您需要首次评论以获取消息 #
# 您需要首次评论以获取消息 #

只显示最新10条未读和已读信息