Whether you're a seasoned developer looking for ways to refine your coding style or a beginner eager to grasp the essentials, this blog post is for you. In this comprehensive guide, we'll explore various best practices that can help you elevate your JavaScript skills to the next level.
1. Adopt a Consistent Coding Style
The first step to improving your JavaScript game is to adopt a consistent coding style. Why is this important? A consistent coding style enhances the readability and maintainability of your code. Think of it as a blueprint that provides structure and coherence to your scripts.
There are several popular coding style guides that you can follow:
- Idiomatic JavaScript, which is community-based
- Google's JavaScript Style Guide
- Airbnb's JavaScript Style Guide
Choose a guide that aligns with your programming style and stick to it throughout your projects, your projects should look like that only one person wrote the code even if many other developers worked on the same project.
2. Naming variables and functions
Next on our list is the naming convention for variables, functions, and other code structures. This practice isn't just about aesthetics or semantics, but it's also about code readability and efficient debugging.
Remember, in JavaScript, the standard is to use camel-case for variables and functions (like myVariableName
), and Pascal case for classes (like MyClassName
).
// ❌ Poorly named variables:
let a = 'John';
let fn = () => console.log('Hello');
// ✅ Descriptive variable names:
let firstName = 'John';
let sayHello = () => console.log('Hello');
3. Use Shorthands but Be Cautious
Shorthands are a great way to write code faster and cleaner, but be wary as they might return surprising results. To prevent such unexpected outcomes, make sure to always check the documentation, find relevant JavaScript code examples, and test the result.
// ❌ Traditional function declaration:
function square1 (num) {
return num * num
}
// ✅ Using arrow functions (shorthand):
const square2 = num => num * num
// ❌ Very long code:
let x
if (y) {
x = y
} else {
x = 'default'
}
// ✅ A more succinct way to achieve the same result using logical OR:
let x = y || 'default'
4. Add meaningful comments to your code
Commenting on your code is like leaving breadcrumbs for your future self or other developers. It helps in understanding the flow and purpose of your code, especially when working on team projects.
However, remember to keep your comments short, and concise, and only include key information.
// ❌ Over-commenting or not commenting at all:
function convertCurrency (from, to) {
// Conversion logic goes here
}
// ✅ Using appropriate comments:
/**
* Converts currency from one type to another
*
* @description Converts one currency to another
* @param {string} from - The currency to convert from
* @param {string} to - The currency to convert to
* @returns {number} - The converted amount
* */
function convertCurrency (from, to) {
// Conversion logic goes here
}
In this example, the first one does not give the developer what is from
and what is to
, but the second example lets the programmer easily understand what are the arguments for and describes what the function does.
5. Follow the SoC principle
To keep things simple and organized, it's best not to use JavaScript for adding direct styling. This is known as separation of concerns (SoC). Instead, use the classList
API to add and remove classes, and define the style rules with CSS.
This way, CSS does all the styling and JavaScript handles all of the other functionalities of your application.
This programming concept is not exclusive to JavaScript, (SoC) the Separation of concerns is a practice to separate functionalities and not mix up different technologies.
To keep it short: CSS should do CSS stuff but JavaScript should not do CSS.
// ❌ Avoid using JavaScript for styling:
let element = document.getElementById('my-element')
element.style.color = 'red'
// ✅ Changing styles by adding/removing classes:
let element = document.getElementById('my-element')
element.classList.add('my-class')
6. Avoid global variables
When declaring local variables, use let
and const
to prevent overriding global variables. Both create block-scoped local variables, but the key difference is that let
can be re-declared while const
can't. So, use them wisely according to your specific needs.
// ❌ Using var for variable declaration:
var x = 1
// ✅ Using let and const for variable declaration:
let x = 1
const y = 2
7. Use for...of
Instead of for
Loops
The for...of
the statement, introduced in ECMAScript 6, is a more efficient alternative to traditional for
loops. It comes with a built-in iterator, eliminating the need to define the variable and the length value. This makes your code cleaner and enhances its performance.
// ❌ Using traditional for loop:
let cities = ['New York', 'Los Angeles', 'Chicago', 'Houston', 'Phoenix']
for (let i = 0; i < cities.length; i++) {
const city = cities[i]
console.log(city)
}
// ✅ Using for of loop:
let cities = ['New York', 'Los Angeles', 'Chicago', 'Houston', 'Phoenix']
for (const city of cities) {
console.log(city)
}
8. Adhere to the Single responsibility principle
One way to adhere to the single responsibility principle is by creating helper functions for common tasks.
These functions should be context-independent so they can be called from any module, enhancing the reusability and modularity of your code.
// ❌ Doing multiple tasks in one function:
function calculateOrderTotal (order) {
let total = 0
for (let i = 0; i < order.items.length; i++) {
total += order.items[i].price * order.items[i].quantity
}
let tax = total * 0.07
return total + tax
}
// ✅ Doing one task in one function:
function calculateSubTotal (items) {
let total = 0
for (let i = 0; i < items.length; i++) {
total += items[i].price * items[i].quantity
}
return total
}
function calculateTax (total) {
return total * 0.07
}
function calculateOrderTotal (order) {
let subTotal = calculateSubTotal(order.items)
let tax = calculateTax(subTotal)
return subTotal + tax
}
9. Understand the Lack of Hoisting in Classes
Unlike functions, classes in JavaScript are not hoisted, meaning you need to declare a class before you call it. This might seem counterintuitive at first, especially if you're accustomed to function hoisting, but it's a fundamental principle that must be understood and respected when using classes in JavaScript.
// ❌ Calling a class before declaration:
const hat = new Hat('Red', 1000)
hat.show()
class Hat {
constructor (color, price) {
this.color = color
this.price = price
}
show () {
console.log(`This ${this.color} hat costs $${this.price}`)
}
}
// ✅ Calling a class after declaration:
class Hat {
constructor (color, price) {
this.color = color
this.price = price
}
show () {
console.log(`This ${this.color} hat costs $${this.price}`)
}
}
const hat = new Hat('Red', 1000)
10. Avoid Mutating Function Arguments
Directly modifying the properties of an object or the values of an array passed as a function argument can lead to undesirable side effects and hard-to-trace bugs. Instead, consider returning a new object or array. This practice aligns well with the principles of functional programming where immutability is key.
// ❌ Mutating function arguments:
function updateName (user) {
user.name = 'bob'
}
let user = { name: 'alice' }
updateName(user)
console.log(user) // { name: 'bob' }
// ✅ Avoid mutating function arguments, return new object instead:
function updateName (user) {
return { ...user, name: 'bob' }
}
let user = { name: 'alice' }
let updatedUser = updateName(user)
console.log(user) // { name: 'alice' }
console.log(updatedUser) // { name: 'bob' }
11. Handle Promises Correctly
In the asynchronous world of JavaScript, promises are a prevalent concept. However, they must be handled correctly to prevent unexpected behaviour. JavaScript provides a try...catch
block that can be used to handle exceptions that may arise during the execution of your asynchronous code.
This way, you ensure errors are caught and dealt with appropriately, maintaining the robustness of your code.
// ❌ Not handling promises correctly:
async function fetchData () {
const response = await fetch('https://jsonplaceholder.typicode.com/todos/1')
const data = await response.json()
return data
}
// ✅ Handling promises correctly:
async function fetchData () {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/todos/1')
const data = await response.json()
return data
} catch (error) {
// Handle your errors here...
throw new Error(error)
}
}
12. Avoid the Over-Nesting in your code
This one is one of the most common mistakes that beginners do, they nest block after block after block, a for loop
inside another loop inside an if.else
inside a try catch
on and on.
Once you know, you have quite a clutter and you don’t know what exactly the code does and where to find the code responsible for what you’re looking for.
Debugging this type of code could be quite difficult and overwhelming, and when other programmers see it, you will confuse them too, not to mention you’ll give off the “Unprofessional” vibe.
// ❌ Nesting code blocks too much and not using the return keyword
function checkNumber (num) {
if (num > 0) {
console.log('Number is positive.')
} else {
if (num < 0) {
console.log('Number is negative.')
} else {
console.log('Number is zero.')
}
}
}
// ✅ Using the return keyword instead of the else statement
function checkNumber (num) {
if (num > 0) {
console.log('Number is positive.')
return
}
if (num < 0) {
console.log('Number is negative.')
return
}
console.log('Number is zero.')
}
13. Optimizing for loops
There is a common way of writing for loops in JavaScript, here is an example of how most developers write loops in JavaScript
var languages = ['Python', 'JavaScript', 'C++', 'Ruby'];
for(var i=0;i<languages.length;i++){
codeIn(languages[i]);
}
For a small array like this, this loop is highly efficient and runs without any problem, but when it comes to larger datasets and an array with thousands of indexes, this approach can slow down your loop process.
What happens is that when you run languages.length
the length of the array will be re-computed each time the loop runs, and the longer your array is, the more inefficient it will be to re-calculate the array length every time the loop runs especially when the length of the array is static which is in most cases.
What you should do instead is store the array length in a different variable and use that variable in your loops, for example:
var languages = ['Python', 'JavaScript', 'C++', 'Ruby'];
var langCount = languages.length;
for(var i=0;i<langCount;i++){
codeIn(languages[i]);
}
With this tweak, the 'length' property is accessed only once, and we use the stored value for subsequent iterations. This improves performance, particularly when dealing with large data sets.
Another elegant approach is to declare a second variable in the pre-loop statement:
var languages = ['Python', 'JavaScript', 'C++', 'Ruby'];
for(var i=0, n=languages.length; i<n; i++){
codeIn(languages[i]);
}
In this approach, the second variable 'n' captures the array's length in the pre-loop statement, thus achieving the same result more succinctly.
Conclusion
Remember, these practices aren't just about writing cleaner and better-structured code. They're also about producing code that's easier to maintain and debug.
Software development is a continuous process. Creating the development code is just the first step of the software life cycle. It's crucial to continually monitor, debug, and improve your code in the production stage.
So there you have it, 13 JavaScript best practices to elevate your coding game. Whether you're a beginner or an experienced developer, adopting these habits will help you write better, more efficient, and maintainable code. Happy coding!
Top comments (11)
??
instead of||
unless this effect is intended.for..of
is actually less performant thanfor..in
and even less thanfor
. The main use offor..of
is to handle Iterators. But if performance doesn't matter too much, why not use the array methods likeforEach/map/reduce
? Know your loops and use the correct one. #9 seems to be missing.var
anymore when there areconst
andlet
.In #3 the two implementations are equivalent, since the more verbose version just has
if (y)
. I would argue that you should do a more explicitif (y === 0)
orif (typeof y === 'undefined')
or whatever is appropriate for your code. Relying on "falsiness" introduces a whole class of bugs if you aren't careful with your code.It's great practice examples. I can remember many things by this.
Thank you
Glad you liked it!
Coding style.
One of the most important part for me is the reduction of cognitive load on my team. It is of the utmost importance that my teammates use their own styles and language dialects. Adoption of a consistent style at a surface sounds great but it piles on busy work for both juniors and some masters. I say, if you can't read other dialects, rather than coercing others to conform, you should focus on becoming a better programmer. Automated
formatting tools, for
example are espe-
cially dangerous in
this
regard. The-
y make the code down
right unread
able sometimes.
A very light formatting standard, however, is always welcome to reduce load on version control.
Naming.
This is an important step of the refactoring phase of a grain of work. It is not a practice, it's more like procedure.
Shorthands and acronyms.
If your grandma knows what it is, you can use it.
Comments
Comments are the first step towards refactoring. Further, if you need to document your code you are already doomed. Well, a 100k line project might benefit from some documentation.
SoC
In our company we don't sell CSS. It is NOT a concern. The only reason why one should break away code at a functional faultline is to reduce noise.
Globals
As long as a module is clean elegant and to the point, this should not have any effect. For anything else one can build application and context level global libraries.
For...of
Looping pattern should be left to the developer. Again, cognitive load reduction. Here, if a dev uses a looping scheme which he is not familiar with, increases the chance of errors.
SRP.
The author is not the only one who does not know what the single responsibility principle is.
Class hoisting
Know your language .. I guess. I do understand why this needs to be pointed out, but why specifically class hoisting? Did author ran into it? The resulting exception is particularly self explanatory.
Mutation
If you can't handle and maintain mutation you should open up a bakery instead.
Promises
The author confuses promises and dirty data/code.
Nesting
Yes, nesting is pain if you can't fit the entire hierarchy on your screen. Turning an 8k monitor portrait is not an elegant solution. Sausage conditions work, but they are sausage. And returns in the middle of functions require your team to be comfortable with it.
Do optimize for loops.
Why the iterator? There is much more precise way to show this.
Conclusion:
Best practices are still a scam. :)
Great! Thanks
Glad you found it useful!
7. Use for...of Instead of for Loops
referring to CanIUse, for...of was not supported by some browers for a long time. First release of Safari, that supported for...of was October 2022. So, without babel it might break your code on older browsers. for...in is generally safe to use or - as mentioned below - use forEach/map/reduce instead
Thanks for this article. Certainly some good points and considerations along!
Under point 11. Handle Promises Correctly I wonder why you recommend to
try-catch
always. IMO there's no reason to try-catch any promise, unless you plan to specifically handle the error thrown - if you do not, then there's no reason to try-catch it and simply throw the same error again. Furthermore, what's the reason you recommend to re-throw the error like this, after one has handled the error acconrdigly:You can simply throw the already defined error - in fact, the error object do not take an error as argument but a string:
Great list!
There's one important thing to keep in mind about mutating function arguments. If a function takes an array and it contains references to objects, returning a new array with references to the same objects (i.e. creating a shallow copy) could cause confusion. This is because a modification to an object in one array will also affect the contents of the other array: they both refer to the same object(s).
If this isn't the intention, one way to do a deep copy is to convert an array/object into JSON, and the parsing it again.
Сongratulations 🥳! Your article hit the top posts for the week - dev.to/fruntend/top-10-posts-for-f...
Keep it up 👍