As front-end development engineer, we can often enhance code readability and make our code look more elegant by paying attention to some small details in our work.
This time, I will share some handy and elegant JavaScript tips that are easy to grasp at first glance.
These tips aim to improve the efficiency and readability of your code.
Technique 1: Reducing if...else Spaghetti Code
When we find ourselves writing functions with more than two if...else
statements, it's time to consider whether there are better optimization methods.
function getPrice(item) {
if (item === 'apple') return 1.0;
else if (item === 'banana') return 0.5;
else if (item === 'orange') return 0.75;
// more conditions...
}
This kind of implementation can clutter the function body with many conditional statements. So, when we want to add another item, we would otherwise need to modify the logic inside the function to include another if...else
statement.
This would, to some extent, violate the Open/Closed Principle (OCP). According to the OCP, when we need to extend a functionality, we should aim to achieve the requirement changes by extending software entities, rather than modifying the existing code.
This is a classic optimization strategy. We can use a data structure similar to Map
to store all items. Here, we can directly create an object to store the data.
const priceMap = {
apple: 1.0,
banana: 0.5,
orange: 0.75,
// more items...
};
function getPrice(item) {
return priceMap[item] || 0; // returns 0 if item not found
}
This approach ensures that when we need to add another item, we don't have to alter the core logic of a function like getPrice
.
Indeed, many prefer using something like foodMap directly where it's needed. Here, Iβm just using a simple example to illustrate this thought process.
const priceMap = new Map();
priceMap.set('apple', 1.0);
priceMap.set('banana', 0.5);
priceMap.set('orange', 0.75);
function getPrice(item) {
return priceMap.get(item) || 0; // returns 0 if item not found
}
Technique 2: Using Pipelining Operations to Replace Redundant Loops
const foods = [
{ name: 'Apple', group: 1 },
{ name: 'Banana', group: 2 },
{ name: 'Carrot', group: 1 },
// more items...
];
const group1Foods = [];
for (let i = 0; i < foods.length; i++) {
if (foods[i].group === 1) {
group1Foods.push(foods[i].name);
}
}
Traditionally, you might use a for loop to iterate over the array, checking each item for its group, and then accumulating the results.
While this method works, it can lead to verbose and less readable code. By using methods like filter
and map
, you can not only make the code more concise but also enhance its semantic clarity.
This way, it's immediately clear that the process involves first filtering
the array and then restructuring
it.
const group1Foods = foods
.filter(food => food.group === 1)
.map(food => food.name);
Technique 3: Using find to Replace Redundant Loops
Continuing with the example above, if we want to search for a specific food in the array of food objects based on a property value, the utility of find
becomes apparent.
Example:
Instead of using a for loop to search for a specific item:
const foods = [
{ name: 'Apple', group: 1 },
{ name: 'Banana', group: 2 },
{ name: 'Carrot', group: 1 },
// more items...
];
let foundFood;
for (let i = 0; i < foods.length; i++) {
if (foods[i].name === 'Banana') {
foundFood = foods[i];
break;
}
}
You can simply use find:
const foundFood = foods.find(food => food.name === 'Banana');
The find
method allows you to quickly locate the first element in an array that satisfies a provided testing function, offering a cleaner and more efficient alternative to traditional loops.
Technique 4: Using includes to Replace Redundant Loops
When you need to check whether an array contains a specific value, using the includes
method can simplify your code significantly. Instead of iterating through the array with a loop to check for the existence of an element, includes
provides a more efficient and readable way to achieve the same result.
Example:
Instead of using a for
loop to determine if an array contains a certain element:
const fruits = ['Apple', 'Banana', 'Carrot'];
let hasBanana = false;
for (let i = 0; i < fruits.length; i++) {
if (fruits[i] === 'Banana') {
hasBanana = true;
break;
}
}
You can simply use includes
:
const hasBanana = fruits.includes('Banana');
Using includes
not only reduces the amount of code but also makes your intention clearer: checking for the presence of a value in an array.
This method offers an elegant solution to what could otherwise be a more verbose process using traditional loops.
It's especially useful when dealing with arrays where you frequently need to perform membership tests, helping you to write cleaner and more maintainable code.
Technique 5: Using a Consistent result Return Variable
As a best practice, especially in smaller functions, you can use a consistent variable name like result for the return value. This makes it clear where the return value is coming from, and it provides a standardized naming convention that you and others can easily recognize.
function calculateTotal(items) {
let result = 0;
for (let i = 0; i < items.length; i++) {
result += items[i].price;
}
return result;
}
Technique 6: Maintaining Object Integrity
When processing data returned from a back-end request, we often handle specific attributes individually. This is especially common when only a few properties need processing. Many developers tend to extract only the necessary attributes for operations, which is the first method.However, this practice can be impractical in the long run.
When it's uncertain whether a function will need additional dependencies later, maintaining the integrity of the entire object is advisable. For instance, if the function getDocDetail
uses properties like icon
and content
now, there might be requirements for title
, date
, and other attributes in the future. Passing the complete object, rather than individual properties, not only reduces the length of the parameter list but also enhances the code's readability and flexibility.
Example:
Instead of extracting and passing only needed attributes:
function getDocDetail(icon, content) {
// process icon and content
}
const doc = { icon: 'icon.png', content: 'Some content', title: 'Document Title', date: '2023-10-15' };
getDocDetail(doc.icon, doc.content);
It's better to pass the entire object:
function getDocDetail(doc) {
const { icon, content } = doc;
// process icon and content
// Access doc.title, doc.date etc. if needed in future
}
const doc = { icon: 'icon.png', content: 'Some content', title: 'Document Title', date: '2023-10-15' };
getDocDetail(doc);
This approach future-proofs your function by allowing easy access to any additional properties without changing its signature. The code becomes more robust and easier to maintain as new requirements emerge, as it avoids the need to continuously alter function parameters. This practice supports modular design principles and contributes to cleaner, scalable codebases.
π Final Thoughts
The JavaScript techniques shared above can effectively enhance code quality and stability. By implementing these strategies, such as reducing redundant loops, maintaining object integrity, and using modern JavaScript methods like filter
, map
, find
, and includes
, you can create cleaner, more efficient, and maintainable code. These practices not only simplify your coding process but also align with best practices, making your applications more robust and adaptable to change.
So, give these tips a try in your next project and experience the improvements first-hand. Happy coding!
Top comments (7)
Thanks for sharing!
Keep going
Perfect for a beginner like me.
Intresting read
This expression uses more advanced vocabulary and phrases to convey a deeper sense of appreciation and admiration for the code shared.
so good
Elegantly Code