How to write JavaScript conditional statements well
When working in JavaScript, we often deal with conditional statements, and here are some tips for writing better/cleaner conditional statements.
1.Javascript content is false
2. We can use ternary expressions to judge if
3. Use array.includes for multiple judgements
4. Less nesting, return early
5. Use default parameters and deconstruction
6. Prefer to traverse objects rather than Switch statements
7. Use array.every & array.some for all/part judgments
8. Strict use, etc
9. To summarize
1. The js content is false
Javascript conditional statements are nothing more than true or false statements, assignments, and other statement operations. Here is an example of a case where Javascript is false:
- false
- null
- undefined
- 0
- “(empty string)
- NAN
2. We can use ternary expressions to judge if
Use in simple situations and avoid complex ones. Allocates or returns statements with ternary operators.
if(x === 10) {
return 'valid';
} else {
return 'invalid';
}
Copy the code
After simplification, the recommended writing:
return x === 10 ? 'valid' : 'invalid';
Copy the code
3. Use array.includes for multiple judgements
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.
4. 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
5. 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).
6. 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.
7. 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
8. Strict use, etc
As typescript becomes more common in our daily development, it complements JS’s weak type validation by using type annotations to locate the type structure of properties. Of course, in normal development, we recommend using strict and so on to avoid unexpected errors in type judgments.
The difference between == and strict ===
=== operator:
- Return false if the two values are of different types
- Return true if both values are of type number and have the same value
- Return true if both values are stirng and both values have the same String content
- Return true if both values are true or false
- Return true if both values refer to the same Object, Array, or function
- Return true if both values are null or undefined
== equivalent operator
- If two values are of the same type, a === comparison is performed, and the comparison value of === is returned
- It is also possible to return true if two values do not have the same type
- Returns true if one value is null and the other is undefined
- If one value is string and the other is number, the string is converted to number and then compared
- If a value is true, it is converted to 1 and compared, and false is converted to 0
console.log( false == null ) // false
console.log( false == undefined ) // false
console.log( false == 0 ) // true
console.log( false == '' ) // true
console.log( false == NaN ) // false
console.log( null == undefined ) // true
console.log( null == 0 ) // false
console.log( null == '' ) // false
console.log( null == NaN ) // false
console.log( undefined == 0) // false
console.log( undefined == '') // false
console.log( undefined == NaN) // false
console.log( 0 == '' ) // true
console.log( 0 == NaN ) // false
Copy the code
9. To summarize
Traversing the object pattern is actually a way of designing the policy pattern. Need to study more design patterns, can provide good code quality.
The resources
Github.com/dawn-plex/t…