• 5 Tips to Write Better Conditionals in JavaScript
  • Ecelyn Yeen(@jecelynYeen)
  • Translation from: Aliyun Translation Group
  • Text link: github.com/dawn-teams/…
  • Translator: Mian Yun
  • Proofread by: Yeshu, Mcskiller

When working in JavaScript, we often deal with conditional statements, and here are 5 tips for writing better/cleaner conditional statements.

1. Use array.includes for multiple decisions

2. Less nesting, return early

3. Use default parameters and deconstruction

4. Tend to traverse objects rather than Switch statements

5. Use array.every & array.some for all/part judgments

6. Summary

1. Use array.includes for multiple decisions

Let’s take a look at this example:

// condition
function test(fruit) {
  if (fruit == 'apple' || fruit == 'strawberry') {
    console.log('red'); }}Copy the code

At first glance, the above example looks fine. What if we had more red fruits with names like cherry and Cranberries? We are going to use more | | to expand the conditional statements?

We can override conditional statements with array. includes (array.includes).

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

  if (redFruits.includes(fruit)) {
    console.log('red'); }}Copy the code

We extract the red fruit condition into an array. This way, the code looks cleaner.

2. Less nesting, Return early

Let’s extend the previous example to include two conditions.

  • If no fruit argument is passed, an error is thrown
  • Accepts the quantity argument and prints it if quantity is greater than 10
function test(fruit, quantity) {
  const redFruits = ['apple'.'strawberry'.'cherry'.'cranberries'];

  // Condition 1: fruit must have a value
  if (fruit) {
    // Condition 2: must be red
    if (redFruits.includes(fruit)) {
      console.log('red');

      // Quantity is greater than 10
      if (quantity > 10) {
        console.log('big quantity'); }}}else {
    throw new Error('No fruit! '); }}// Test results
test(null); // error: No fruits
test('apple'); // print: red
test('apple'.20); // print: red, big quantity
Copy the code

In the code above, we have:

  • Filter out an invalid if/else statement
  • Layer 3 if nested statement (conditions 1, 2 & 3)

The rule I personally follow is to Return as soon as possible when an invalid condition is found.

/_ When an invalid statement is found, Return _/ as soon as possiblefunction test(fruit, quantity) {
  const redFruits = ['apple'.'strawberry'.'cherry'.'cranberries'];

  // Condition 1: Throw an error early
  if(! fruit)throw new Error('No fruit! ');

  // Condition 2: must be red
  if (redFruits.includes(fruit)) {
    console.log('red');

    // Condition 3: must be of high quality
    if (quantity > 10) {
      console.log('big quantity'); }}}Copy the code

As a result, we have one less layer of nested statements. This coding style is great, especially if you have long if statements (it’s not cool to think you need to scroll down to the bottom to know there are else statements)

We can further reduce if nesting by inverting the judgment condition & return early. Let’s see how we deal with statement 2:

/_ When an invalid statement is found, Return _/ as soon as possiblefunction test(fruit, quantity) {
  const redFruits = ['apple'.'strawberry'.'cherry'.'cranberries'];

  // Condition 1: Throw an error early
  if(! fruit)throw new Error('No fruit! ');
  // Condition 2: Stop executing when the fruit is not red
  if(! redFruits.includes(fruit))return; 

  console.log('red');

  // Condition 3: must be of high quality
  if (quantity > 10) {
    console.log('big quantity'); }}Copy the code

By inverting judgment condition 2, our code avoids nested statements. This technique is useful when we need to make long logical judgments, especially if we want to be able to stop processing if the condition is not met.

And it’s not hard to do. Ask yourself, is this version (no nesting) better and more readable than the previous version (two-layer conditional nesting)?

But for me, I’ll keep the previous version (with two layers of nesting). This is because:

  • The code is shorter and more straightforward, and the inclusion of if nesting is clearer
  • Reversing judgment conditions may increase the burden of thinking (increase cognitive load)

Therefore, try to minimize nesting and return early, but not too much. If you’re interested, check out an article and StackOverflow discussion on this topic.

  • Avoid Else, Return Early by Tim Oxley
  • StackOverflow discussion on if/else coding style

3. Use default parameters and deconstruction

I’m guessing you’ll be familiar with the following code: in JavaScript we always need to check for null/undefined and specify default values:

function test(fruit, quantity) {
  if(! fruit)return;
  // If the quantity argument is not passed, set the default value to 1
  const q = quantity || 1; 

  console.log(`We have ${q} ${fruit}! `);
}

//test results
test('banana'); // We have 1 banana!
test('apple'.2); // We have 2 apple!
Copy the code

In fact, we can eliminate the variable q by declaring a default function parameter.

function test(fruit, quantity = 1) {
  // If the quantity argument is not passed, set the default value to 1
  if(! fruit)return;
  console.log(`We have ${quantity} ${fruit}! `);
}

//test results
test('banana'); // We have 1 banana!
test('apple'.2); // We have 2 apple!
Copy the code

That’s more intuitive, right? Note that each declaration has its own default parameters.

For example, we could also assign a default value to fruit :function test(fruit = ‘unknown’, quantity = 1).

What if fruit is an object? Can we assign a default parameter?

function test(fruit) { 
  // Prints the fruit value when the value exists
  if (fruit && fruit.name)  {
    console.log (fruit.name);
  } else {
    console.log('unknown'); }}//test results
test(undefined); // unknown
test({ }); // unknown
test({ name: 'apple'.color: 'red' }); // apple
Copy the code

Looking at the example above, we want to print a name property that might exist in a Fruit object. Otherwise we print unknown. We can avoid judging the condition fruit && fruit.name by default arguments and deconstruction

// Destruct - Just get the name attribute
// Assign the default value to an empty object
function test({name} = {}) {
  console.log (name || 'unknown');
}

// test results
test(undefined); // unknown
test({ }); // unknown
test({ name: 'apple'.color: 'red' }); // apple
Copy the code

Since we only need the name attribute, we can deconstruct the argument with {name}, and then we can use the variable name instead of fruit.name.

We also need to declare an empty object {} as the default. If we don’t, you’ll get an error that can’t be deconstructed from undefined or null when you execute test. Because undefined has no name property.

If you don’t mind using third-party libraries, here are some ways to reduce null checking:

  • Use the Lodash get function
  • Using Facebook’s open source IDX library (with Babeljs)

Here is an example using Lodash:

function test(fruit) {
  // Get the attribute name, if the attribute name is not available, assign the default value unknown
  console.log(__.get(fruit, 'name'.'unknown'); 
}

// test results
test(undefined); // unknown
test({ }); // unknown
test({ name: 'apple'.color: 'red' }); // apple
Copy the code

You can run demo code in Jsbin. In addition, if you are a fan of functional programming, you may choose to use Lodash FP, the functional version of Lodash (change the method to get or getOr).

4. Favor object traversal over Switch statements

Let’s look at the following example where we want to print fruit according to color:

function test(color) {
  // Use the conditional statement to find the corresponding fruit color
  switch (color) {
    case 'red':
      return ['apple'.'strawberry'];
    case 'yellow':
      return ['banana'.'pineapple'];
    case 'purple':
      return ['grape'.'plum'];
    default:
      return[]; }}// test results
test(null); / / []
test('yellow'); // ['banana', 'pineapple']
Copy the code

The code above looks error-free, but I’ve found some dead weight. The syntax looks much cleaner with object traversal to achieve the same result:

const fruitColor = {
  red: ['apple'.'strawberry'].yellow: ['banana'.'pineapple'].purple: ['grape'.'plum']};function test(color) {
  return fruitColor[color] || [];
}
Copy the code

Or you can use Map to achieve the same result:

  const fruitColor = new Map()
    .set('red'['apple'.'strawberry'])
    .set('yellow'['banana'.'pineapple'])
    .set('purple'['grape'.'plum']);

function test(color) {
  return fruitColor.get(color) || [];
}
Copy the code

Map is an object type implemented after the ES2015 specification that allows you to store the values of keys and values.

But should we ban the use of switch statements? The answer is don’t limit yourself. Personally, I use object traversal whenever possible, but I don’t stick to it strictly and use a way that makes more sense for the current scene.

Todd Motto has a more in-depth article on switch statements versus object traversal, which you can read here

TL; DR; Refactoring grammar

In the example above, we were able to refactor our code with array.filter to achieve the same effect.

 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 test(color) {
  return fruits.filter(f= > f.color == color);
}
Copy the code

There are more than one way to achieve the same result, and we’ve shown four of them above.

5. Use array.every & array.some for all/part judgments

This last tip is more about using JavaScript Array’s built-in methods to reduce the number of lines of code. Looking at the following code, we want to check if all fruit is red:

const fruits = [
    { name: 'apple'.color: 'red' },
    { name: 'banana'.color: 'yellow' },
    { name: 'grape'.color: 'purple'}];function test() {
  let isAllRed = true;

  // Condition: All fruits are red
  for (let f of fruits) {
    if(! isAllRed)break;
    isAllRed = (f.color == 'red');
  }

  console.log(isAllRed); // false
}
Copy the code

The code is so long! We can reduce the number of lines of code by 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
}
Copy the code

It’s much simpler now, isn’t it? In the same way, if we want to test for the presence of red fruit, we can do this using array.some.

const fruits = [
    { name: 'apple'.color: 'red' },
    { name: 'banana'.color: 'yellow' },
    { name: 'grape'.color: 'purple'}];function test() {
  // Condition: any fruit is red
  const isAnyRed = fruits.some(f= > f.color == 'red');

  console.log(isAnyRed); // true
}
Copy the code

6. Summary

Let’s produce more readable code together. I hope you can learn something from this article.

That’s all. Happy coding!