summary: let us look at the following common condition expression scenarios, how to write beautifully!
Share this article from Huawei cloud community " how to write beautiful conditional expression - JavaScript achieve articles ", the original author: Charles.
Conditional expression is a problem that we can never avoid in the coding process, and it is also the most convenient thing for us to deceive the number of lines of code (dog head.jpg), but as a programmer, we also have to pursue our own "sindya ", let's take a look at the following common conditional expression scenarios, how to write beautifully!
- Multiple conditional statements
- Multi-attribute object
- Replace the switch statement
- Default parameters and deconstruction
- Match all or part of the conditions
- Use optional chain and Nullish merge
Multiple conditional statements
Use Array.includes for multiple conditional statements
for example
function printAnimals(animal) {
if (animal === "dog" || animal === "cat") {
console.log(`I have a ${animal}`);
}
}
console.log(printAnimals("dog")); // I have a dog
This way of writing seems to be no problem when there are fewer conditions. At this time, we only have 2 kinds of animals, but what if we have more conditions to judge (more animals)? If we continue to expand the conditions of judgment, the code will become difficult to maintain and the logic will be unclear.
Solution
You can use Array.includes to rewrite conditional statements
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
Here, we created an array of animals in order to extract the conditions separately from the rest of the code. Now, if we want to check any other animals, all we need to do is add a new array item.
We can also use the animals variable outside the scope of this function to reuse it in other places in the code. This is a way to write code that is clearer, easier to understand and maintain. Isn't it?
Multi-attribute object
This is a very good technique to compress your code and make it look more concise. Let's take the previous example as an example and add more conditions. What if the animal is not a simple string, but an object with certain properties?
So the requirements now are:
- If there are no animals, throw an error
- Print the type of animal
- Print the name of the animal
- Print the sex of the animal
const printAnimalDetails = (animal) => {
let result; // declare a variable to store the final value
// condition 1: check if animal has a value
if (animal) {
// condition 2: check if animal has a type property
if (animal.type) {
// condition 3: check if animal has a name property
if (animal.name) {
// condition 4: check if animal has a gender property
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'
The above code works very well, but the code is very long and difficult to maintain. If you don't use the hint tool, you may waste some time to determine the position of the closing parenthesis. Imagine what will happen if the code is more complex logic. A lot of if...else statements!
We can use ternary operators, && conditions, etc. to refactor the above functions, but let us use multiple return statements to write more precise code.
const printAnimalDetails = ({ type, name, gender } = {}) => {
if (!type) return "No animal type";
if (!name) return "No animal name";
if (!gender) return "No animal gender";
// Now in this line of code, we're sure that we have an animal with all //the three properties here.
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'
In the refactored version, deconstruction and default parameters are also included. The default parameters ensure that if we pass undefined as a parameter to the method, we still have a value to deconstruct, here is an empty object {}.
Usually, the code is written between these two methods.
for example
function printVegetablesWithQuantity(vegetable, quantity) {
const vegetables = ["potato", "cabbage", "cauliflower", "asparagus"];
// condition 1: vegetable should be present
if (vegetable) {
// condition 2: must be one of the item from the list
if (vegetables.includes(vegetable)) {
console.log(`I like ${vegetable}`);
// condition 3: must be large quantity
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'
Now, we have:
- Filter if/else statements for invalid conditions
- 3 levels of nested if statements (condition 1, 2 and 3)
- A general rule is to return as soon as possible when an invalid condition is found.
A general rule is to return as soon as possible when an invalid condition is found
function printVegetablesWithQuantity(vegetable, quantity) {
const vegetables = ["potato", "cabbage", "cauliflower", "asparagus"];
// condition 1: throw error early
if (!vegetable) throw new Error("No vegetable from the list!");
// condition 2: must be in the list
if (vegetables.includes(vegetable)) {
console.log(`I like ${vegetable}`);
// condition 3: must be a large quantity
if (quantity >= 10) {
console.log("I have bought a large quantity");
}
}
}
By doing this, we reduce the level of a nested statement. This coding style is good, especially when using long if statements. By reversing the condition and returning early, we can further reduce the nested if.
Please see how condition 2 is done below:
function printVegetablesWithQuantity(vegetable, quantity) {
const vegetables = ["potato", "cabbage", "cauliflower", "asparagus"];
if (!vegetable) throw new Error("No vegetable from the list!");
// condition 1: throw error early
if (!vegetables.includes(vegetable)) return;
// condition 2: return from the function is the vegetable is not in
// the list
console.log(`I like ${vegetable}`);
// condition 3: must be a large quantity
if (quantity >= 10) {
console.log("I have bought a large quantity");
}
}
By reversing the condition of condition 2, the code no longer has nested statements. This technique is useful when we have many conditions and want to stop further processing when any particular condition is not met.
Therefore, always aim to reduce nesting and return as soon as possible, but don't overdo it.
Replace the switch statement
Let's take a look at the following example, we want to print fruits according to color:
function printFruits(color) {
// use switch case to find fruits by color
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']
There is no error in the implementation of the above code, but it is very verbose. The same result can be achieved using a more concise syntax.
// use object literal to find fruits by color
const fruitColor = {
red: ["apple", "strawberry"],
yellow: ["banana", "pineapple"],
purple: ["grape", "plum"],
};
function printFruits(color) {
return fruitColor[color] || [];
}
Similarly, you can also use Map to achieve:
// use Map to find fruits by color
const fruitColor = new Map()
.set("red", ["apple", "strawberry"])
.set("yellow", ["banana", "pineapple"])
.set("purple", ["grape", "plum"]);
function printFruits(color) {
return fruitColor.get(color) || [];
}
Map is an object type available since ES5, which allows key-value storage.
For the example above, you can use Array.filter to achieve the same result.
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);
}
Default parameters and deconstruction
When using JavaScript, we always need to check for null/undefined and assign default values or break the compilation.
function printVegetablesWithQuantity(vegetable, quantity = 1) {
// if quantity has no value, assign 1
if (!vegetable) return;
console.log(`We have ${quantity} ${vegetable}!`);
}
//results
}
printVegetablesWithQuantity('cabbage'); // We have 1 cabbage!
printVegetablesWithQuantity('potato', 2); // We have 2 potato!
What if the vegetable is an object? Can we assign a default parameter?
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
In the above example, we want to print the vegetable name (if it is available) or print unknown.
We can avoid the condition if (vegetable && vegetable.name){} by using default parameters & destructuring.
// 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
Because we only need the attribute name, we can use {name} to change the structure of the parameter, and then we can use name as a variable in the code instead of vegetable.name.
We will also assign an empty object {} as the default value, otherwise when executing printVegetableName(undefined), it will give an error-Cannot destructure property name of undefined or null, because there is no name property in undefined.
Match all or part of the conditions
We can reduce the number of lines of code by using these Array methods.
In the following code, we want to check whether all fruits are red:
const fruits = [
{ name: "apple", color: "red" },
{ name: "banana", color: "yellow" },
{ name: "grape", color: "purple" },
];
function test() {
let isAllRed = true;
// condition: all fruits must be red
for (let f of fruits) {
if (!isAllRed) break;
isAllRed = f.color == "red";
}
console.log(isAllRed); // false
}
The above code is too verbose, we can reduce the lines of code by using Array.every:
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
}
Similarly, if we want to test whether any fruit is red, we can use Array.some:
const fruits = [
{ name: "apple", color: "red" },
{ name: "banana", color: "yellow" },
{ name: "grape", color: "purple" },
];
function test() {
// condition: if any fruit is red
const isAnyRed = fruits.some((f) => f.color == "red");
console.log(isAnyRed); // true
}
Use optional chain and Nullish merge
These two functions are very useful for writing more concise conditions in JavaScript. At the time of writing, they are not fully supported and may need to be compiled with Babel.
Optional links can handle a tree-like structure without the need to explicitly check whether intermediate nodes exist, and Nullish combined with optional links is very effective to ensure that there is no default value for the node.
for example:
const car = {
model: "Fiesta",
manufacturer: {
name: "Ford",
address: {
street: "Some Street Name",
number: "5555",
state: "USA",
},
},
};
// to get the car model
const model = (car && car.model) || "default model";
// to get the manufacturer street
const street =
(car &&
car.manufacturer &&
car.manufacturer.address &&
car.manufacturer.address.street) ||
"default street";
// request an un-existing property
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
Therefore, if we want to print it out, if the car manufacturer is from the United States, the code should look like this:
const isManufacturerFromUSA = () => {
if (
car &&
car.manufacturer &&
car.manufacturer.address &&
car.manufacturer.address.state === "USA"
) {
console.log("true");
}
};
checkCarManufacturerState(); // 'true'
You can clearly see how confusing this can become for more complex object structures. There are some third-party libraries, such as lodash or idx, which have their own functions. For example, lodash has a _.get method. However, this feature is introduced in the JavaScript language itself.
Here is how these new features work:
// to get the car model
const model = car?.model ?? "default model";
// to get the manufacturer street
const street = car?.manufacturer?.address?.street ?? "default street";
// to check if the car manufacturer is from the USA
const isManufacturerFromUSA = () => {
if (car?.manufacturer?.address?.state === "USA") {
console.log("true");
}
};
Currently in Stage 3 stage.
The above is some sharing of conditional expressions based on JavaScript, I hope it can be helpful to you~
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。