六个关于JavaScript条件判断的编程技巧

698 阅读7分钟

看到一篇讲Javascript判断分支优化的文章,转载并且尝试翻译一下,欢迎轻拍。

什么是条件分支

在任何编程语言中,代码需要根据输入内容,作出判断并且执行相应的行为。举例来说,在一个游戏中,如果玩家的生命值为零,那么游戏就会结束。在一个天气应用软件中,白天就会显示一个日出的图片,夜里会显示星星月亮的图案。在本文中,我们将会探索所谓的条件分支语句如何在JavaScript中工作。

如果你用JavaScript语言工作,你将会写一堆含有条件语句的代码。这些条件语句一开始容易理解,但是更重要的不仅仅是写一堆 if/esle 判断语句。这里有一些有用的小窍门来帮助我们写更好更加简洁的条件分支判断。

内容目录

  1. Array.includes
  2. Early exit / Return early
  3. Object Literal or Map instead of Switch Statement
  4. Default Parameters and Destructuring
  5. Match all/partial criteria using Array.every & Array.some
  6. Use Optional Chaining and Nullish Coalescing

1. Array.includes

当判断条件很多的时候,使用Array.includes 例如:

function printAnimals(animal) {
    if (animal === 'dog' || animal === 'cat') {
      console.log(`I have a ${animal}`);
    }
}
    
console.log(printAnimals('dog')); // I have a dog

上面的代码在只需要检查两个动物的时候很好。但是如果我们不清楚用户会输入什么,或者我们加入了别种动物呢? 如果我们用更多的 OR 语句扩展,代码将会变得更加难维护,更加不清楚。 思路: 我们用Array.includes重写上面的判断条件。

function printAnimals(animal) {
    const animals = ['dog', 'cat', 'hamster', 'turtle']; 

    if (animals.includes(animal)) {
        console.log(`I have a ${animal}`);
    }
}

console.log(printAnimals('hamster')); // I have a hamster

这里,我们创造了一个animals数组,这样条件判断就从剩余的代码中被抽离开来。现在,如果我们想要做对其他任何一种动物的检查,我们仅仅需要增加一个数组项。

我们也能在这个function外的其他地方使用animals变量。这是一种有利书写更加简洁易于理解与维护的代码的方法。

2. Early exit / Return early

这是一个非常酷的窍门来浓缩你的代码,使它看起来更加简洁。我记得我开始正式工作的时候,我第一天就学习用早退出(返回)来写判断条件。 让我们拿出之前的例子,增加一些条件。如果把string类型的animal替换成有明确属性的对象。 所以现在的需求是:

  • 如果没有animal,抛错
  • 打印动物的类型
  • 打印动物的名字
  • 打印动物的性别
const printAnimalDetails = animal => {
  let result; // 声明一个变量来存储最终的值

  // 条件1: 检查animal是否存在
  if (animal) {

    // 条件2: 检查animal是否有type属性
    if (animal.type) {

      // 条件3: 检查animal是否有名字属性
      if (animal.name) {

        // 条件4: 检查animal是否有性别属性
        if (animal.gender) {
          result = `${animal.name} is a ${animal.gender} ${animal.type};`;
        } else {
          result = "No animal gender";
        }
      } else {
        result = "No animal name";
      }
    } else {
      result = "No animal type";
    }
  } else {
    result = "No animal";
  }

  return result;
};

console.log(printAnimalDetails()); // 'No animal'

console.log(printAnimalDetails({ type: "dog", gender: "female" })); // 'No animal name'

console.log(printAnimalDetails({ type: "dog", name: "Lucy" })); // 'No animal gender'

console.log(
  printAnimalDetails({ type: "dog", name: "Lucy", gender: "female" })
); // 'Lucy is a female dog'

上面的代码看起来正常运行,但是太长也不易维护。如果不用linting工具,会花费很多时间在花括号配对上面。想象一下如果代码有更加复杂的逻辑,会增加更多的 if..else语句。

我们可以用三元表达式, && 条件等方法重构上面的function,但是我们可以通过使用多重return语句写出更加简洁的代码。

const printAnimalDetails = ({type, name, gender } = {}) => {
  if(!type) return 'No animal type';
  if(!name) return 'No animal name';
  if(!gender) return 'No animal gender';

// 现在在这行代码里面,我们确定有一个animal 包含三个属性。
  return `${name} is a ${gender} ${type}`;
}

console.log(printAnimalDetails()); // 'No animal type'

console.log(printAnimalDetails({ type: dog })); // 'No animal name'

console.log(printAnimalDetails({ type: dog, gender: female })); // 'No animal name'

console.log(printAnimalDetails({ type: dog, name: 'Lucy', gender: 'female' })); // 'Lucy is a female dog'

在重构的版本中,解构赋值与默认参数也包含在内。默认参数保证即使我们给方法传了一个undefined给方法,依旧会有一个值可以解构,是一个空对象。

另外一个例子:

  function printVegetablesWithQuantity(vegetable, quantity) {
  const vegetables = ['potato', 'cabbage', 'cauliflower', 'asparagus'];

  // 条件 1: vegetable 应该存在
   if (vegetable) {
     // 条件 2: 必须是数组中的一类
     if (vegetables.includes(vegetable)) {
       console.log(`I like ${vegetable}`);
       // 条件 3: 必须达到一定数量
       if (quantity >= 10) {
         console.log('I have bought a large quantity');
       }
     }
   } else {
     throw new Error('No vegetable from the list!');
   }
 }

 printVegetablesWithQuantity(null); //  No vegetable from the list!
 printVegetablesWithQuantity('cabbage'); // I like cabbage
 printVegetablesWithQuantity('cabbage', 20); 
 // 'I like cabbage`
 // 'I have bought a large quantity'

现在,我们:

  1. if/else语句过滤了不正确的条件
  2. 嵌套层次的if语句 (condition 1, 2 & 3)

当发现一个false的条件,遵守一个通用的规则来尽早返回

  function printVegetablesWithQuantity(vegetable, quantity) {

  const vegetables = ['potato', 'cabbage', 'cauliflower', 'asparagus'];

   // 条件 1: 尽早抛错
   if (!vegetable) throw new Error('No vegetable from the list!');

   // 条件 2: 必须在列表上
   if (vegetables.includes(vegetable)) {
      console.log(`I like ${vegetable}`);

     // 条件 3: 必须数目惊人
      if (quantity >= 10) {
        console.log('I have bought a large quantity');
      }
   }
 }

3. Object Literal or Map instead of Switch Statement

让我们查看一下下面的例子,我们想要根据颜色打印水果:

function printFruits(color) {
  // 使用 switch case 来根据颜色找到水果
  switch (color) {
    case 'red':
      return ['apple', 'strawberry'];
    case 'yellow':
      return ['banana', 'pineapple'];
    case 'purple':
      return ['grape', 'plum'];
    default:
      return [];
  }
}

printFruits(null); // []
printFruits('yellow'); // ['banana', 'pineapple']

这上面的代码没有错误,但是很冗长。同样的结果可以使用有更加简洁语法的对象字面量:

// 使用对象字面量来根据颜色找到水果
  const fruitColor = {
    red: ['apple', 'strawberry'],
    yellow: ['banana', 'pineapple'],
    purple: ['grape', 'plum']
  };

function printFruits(color) {
  return fruitColor[color] || [];
}

同样的,你可以使用Map来达到同样的效果:

// 使用 Map 来根据颜色找水果
  const fruitColor = new Map()
    .set('red', ['apple', 'strawberry'])
    .set('yellow', ['banana', 'pineapple'])
    .set('purple', ['grape', 'plum']);

function printFruits(color) {
  return fruitColor.get(color) || [];
}

Map允许存储键值对,是ES2015(ES6)的新内容。 对于上面的例子,相同的结果也可以用Array.filter来实现:

 const fruits = [
    { name: 'apple', color: 'red' }, 
    { name: 'strawberry', color: 'red' }, 
    { name: 'banana', color: 'yellow' }, 
    { name: 'pineapple', color: 'yellow' }, 
    { name: 'grape', color: 'purple' }, 
    { name: 'plum', color: 'purple' }
];

function printFruits(color) {
  return fruits.filter(fruit => fruit.color === color);
}

4. Default Parameters and Destructuring

当我们用JavaScript工作的时候,我们需要检查null/undefined值,并且需要赋默认值。

  function printVegetablesWithQuantity(vegetable, quantity = 1) { 
  //如果 quantity 没有值, 赋值 1

  if (!vegetable) return;
    console.log(`We have ${quantity} ${vegetable}!`);
  }

  //results
  printVegetablesWithQuantity('cabbage'); // We have 1 cabbage!
  printVegetablesWithQuantity('potato', 2); // We have 2 potato!

如果vegetable是一个对象,我们可以赋值一个默认对象参数吗?

  function printVegetableName(vegetable) { 
    if (vegetable && vegetable.name) {
     console.log (vegetable.name);
   } else {
    console.log('unknown');
   }
 }

 printVegetableName(undefined); // unknown
 printVegetableName({}); // unknown
 printVegetableName({ name: 'cabbage', quantity: 2 }); // cabbage

在上面的例子中,我们想要打印vegatable的name,如果有就打印名字,没有就输出unknown。

我们可以通过使用默认赋值和解构赋值去掉 if (vegetable && vegetable.name) {}这一判断。

  // destructing - get name property only
  // assign default empty object {}

  function printVegetableName({name} = {}) {
   console.log (name || 'unknown');
 }


 printVegetableName(undefined); // unknown
 printVegetableName({ }); // unknown
 printVegetableName({ name: 'cabbage', quantity: 2 }); // cabbage

我们只需要name这个属性,我们可以使用{name}将参数解构, 当我们可以在代码里面使用name作为一个变量,取代vegatable.name。

我们可以将一个空对象设置为一个默认值,否则它在执行时会报错。printVegetableName(undefined) - Cannot destructure property name of undefined or null因为在undefined中,不存在name属性。

5. Match all/partial criteria using Array.every & Array.some

我们可以通过数组的方法削减代码行数。查看下面的代码,我们想要检查是否是否所有的水果都是红色:

const fruits = [
    { name: 'apple', color: 'red' },
    { name: 'banana', color: 'yellow' },
    { name: 'grape', color: 'purple' }
  ];

function test() {
  let isAllRed = true;

  // 条件: 所有的水果必须都是红色
  for (let f of fruits) {
    if (!isAllRed) break;
    isAllRed = (f.color == 'red');
  }

  console.log(isAllRed); // false
}

这代码依旧太长,我们可以通过Array.every缩减代码行数:

const fruits = [
    { name: 'apple', color: 'red' },
    { name: 'banana', color: 'yellow' },
    { name: 'grape', color: 'purple' }
  ];

function test() {
  // 条件: 所有水果必须为红色
  const isAllRed = fruits.every(f => f.color == 'red');

  console.log(isAllRed); // false
}

在同样的方法中,如果我们想要测试是否一些水果是红色,我们可以使用Array.some在一行代码中达成目标。

const fruits = [
    { name: 'apple', color: 'red' },
    { name: 'banana', color: 'yellow' },
    { name: 'grape', color: 'purple' }
];

function test() {
  // 条件: 如果部分水果是红色
  const isAnyRed = fruits.some(f => f.color == 'red');

  console.log(isAnyRed); // true
}

6.Use Optional Chaining and Nullish Coalescing

这两个方法在编写更加简洁判断JavaScript条件的时候会非常有用。他们目前不是完全被支持的,你需要使用Babel来编译。 Optional chaining(可选链式调用,下面简称OC)让我们不需要检查中间节点是否存在直接处理树状结构。Nulllish coalescing(空值合并,下面简称NC)在跟OC组合来确保默认值是否是一个不存在的值这一作用上作用明显。(文中介绍比较简单,可以参考 这里)下面有个例子:

   const car = {
    model: 'Fiesta',
    manufacturer: {
    name: 'Ford',
    address: {
      street: 'Some Street Name',
      number: '5555',
      state: 'USA'
      }
    }
  } 

  // 获取car的model
  const model = car && car.model || 'default model';

  // 获取manufactur address
  const street = car && car.manufacturer && car.manufacturer.address && 
  car.manufacturer.address.street || 'default street';

  // 获取一个不存在的值
  const phoneNumber = car && car.manufacturer && car.manufacturer.address 
  && car.manufacturer.phoneNumber;

  console.log(model) // 'Fiesta'
  console.log(street) // 'Some Street Name'
  console.log(phoneNumber) // undefined

你会发现代码结构异常复杂,这里有很多第三方的库lodash, idx有他们自己的方法。比如lodash有_.get方法。但是如果JS有自己的语言去实现就特别酷。 下面显示新的feture如何使用:

 //  获取car的model
 const model = car?.model ?? 'default model';

 // 获取manufacturer street
 const street = car?.manufacturer?.address?.street ?? 'default street';

 // 检查car manufacturer是否从美国来
 const isManufacturerFromUSA = () => {
   if(car?.manufacturer?.address?.state === 'USA') {
     console.log('true');
   }
 }

这代码看起来就很帮而且易于维护。期待这个新特性可以尽早让我们使用。

总结

让我们学习和尝试新技巧和新技术使得写出更加简洁和易于维护的代码,因为一段时间以后,一串冗余的判断条件代码那就是搬起石头砸自己的脚。文章出处