Summary of Optimization Skills of Conditional Statements in JavaScript

  • 2021-10-11 17:35:38
  • OfStack

Use Array. includes for multiple conditions


function test(fruit) {
 if (fruit == 'apple' || fruit == 'strawberry') {
 console.log('red');
 }
}

The above example looks good. However, if there are more red fruits to judge, such as cherries and cranberries, should we expand this expression with more?

We can override the above conditions with Array. includes!


function test(fruit) {
 const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries'];
 if (redFruits.includes(fruit)) {
 console.log('red');
 }
}

We fetch the condition into an array. After doing so, the code looks cleaner.

Less nesting, early return

Extend the previous example to include two other conditions:

If no fruit (name) is provided, an error is thrown.

If the number of (red fruits) exceeds 10, accept and print.

Looking at the code above, we have:

1 set of if/else statements that filter invalid conditions

Layer 3 if nested statements (conditions 1, 2, and 3)

The general rule is to return early when an invalid condition is found.


/_ return early when invalid conditions found _/
function test(fruit, quantity) {
 const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries'];

 // condition 1: throw 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 big quantity
 if (quantity > 10) {
  console.log('big quantity');
 }
 }
}

In this way, we lose one layer of nesting. This coding style is good, especially if you have a very long if statement (imagine 1, you need to scroll to the bottom to know that there is another else statement, which is not good).

By reversing conditions and returning early, we can go one step further to reduce nesting. Look at condition 2 below, how do we do it:


/_ return early when invalid conditions found _/
 
function test(fruit, quantity) {
 const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries'];
 
 if (!fruit) throw new Error('No fruit!'); // condition 1: throw error early
 if (!redFruits.includes(fruit)) return; // condition 2: stop when fruit is not red
 
 console.log('red');
 
 // condition 3: must be big quantity
 if (quantity > 10) {
  console.log('big quantity');
 }
}

By reversing the condition of condition 2, our code now has no nested statements. This technique is useful when we have a long logic to process, and when a condition is not met, we want to stop processing in the next step.

However, this is not a strict rule. Ask yourself, is this version (without nesting) better and easier to read than the previous version (nested condition 2)?

For me, I will keep it as the previous version (condition 2 and nesting). This is because:
The code is short and straightforward, if nested, the code is clearer, and reversing conditions may lead to more thinking process (increasing cognitive burden)!

Therefore, always aim for less nesting and early return, but don't overdo it.

Use default function arguments and deconstruction

When using JavaScript, you always need to check the null or undefined values and assign default values:


function test(fruit, quantity) {
 if (!fruit) return;
 const q = quantity || 1; 
}

//test results
test('banana');
test('apple', 2);

In fact, you can eliminate the variable q by specifying the default function parameters.


function test(fruit, quantity = 1) {
 if (!fruit) return;
}

//test results
test('banana');
test('apple', 2);

Note that each parameter can have its own default function parameter. For example, we can also assign a value to fruit: function test (fruit = 'unknown', quantity = 1).

If our fruit is one object:


function test(fruit) {
 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

If fruit. name is available, we will print the fruit name, otherwise we will print unknown. We can avoid using fruit with default function parameters and deconstruction on conditions & & fruit. name.


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

//test results
test(undefined); // unknown
test({ }); // unknown
test({ name: 'apple', color: 'red' }); // apple

Because we only need the attribute name in the fruit, we can use {name} to deconstruct, and then we can use name as a variable in the code instead of fruit. name.

We also specify the empty object {} as the default value. If we don't, you will get an error when you execute test (undefined) and can't deconstruct the property names of undefined or null. Because there is no name attribute in undefined.

Select Map or object literals instead of Switch statements

We want to print fruit names based on color:


function test(color) {
 // use switch case to find fruits in 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']

There seems to be nothing wrong with the above code, but it is quite verbose. The same result can be achieved with object literals and more concise syntax:


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

Alternatively, you can use Map to achieve the same result:


function test(fruit) {
 const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries'];
 if (redFruits.includes(fruit)) {
 console.log('red');
 }
}
0

Map is an object type available after ES 2015, allowing you to store key-value pairs.

For the example above, we can actually refactor the code to get the same result using Array. filter.


function test(fruit) {
 const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries'];
 if (redFruits.includes(fruit)) {
 console.log('red');
 }
}
1

There is always more than one way to achieve the same effect. Four examples of the same effect are shown.

Use Array. every in whole or in part & Array. Conditions of some

Use the new Javascript array function to reduce the number of lines of code. Looking at the following code, we want to check whether all the fruits are red:


function test(fruit) {
 const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries'];
 if (redFruits.includes(fruit)) {
 console.log('red');
 }
}
2

The code is too long! We can use Array. every to reduce the number of rows:


const fruits = [
  { name: 'apple', color: 'red' },
  { name: 'banana', color: 'yellow' },
  { name: 'grape', color: 'purple' }
 ];
 
function test() {
 // condition: short way, all fruits must be red
 const isAllRed = fruits.every(f => f.color == 'red');
 
 console.log(isAllRed); // false
}

It's much cleaner now, isn't it? Similarly, if we want to use 1 line of code to determine whether any 1 fruit is red, we can use Array. some.


function test(fruit) {
 const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries'];
 if (redFruits.includes(fruit)) {
 console.log('red');
 }
}
4

Related articles: