Best practices for writing clean, maintainable JavaScript

| 9 min. (1777 words)

The world’s biggest language comes with a huge collection of conventions and guidelines from the community’s collective wisdom. Following JavaScript best practices can help achieve faster page loads and better performance, improve code readability, and make maintenance and debugging easier. Carefully crafted code can also prevent errors and security issues, especially if it’s complemented with real-time diagnostic tools such as JavaScript error monitoring.

In this article, we’ll look into a selection of JavaScript best practices that you can use in frontend development.

JavaScript styling and naming best practices

1. Choose a JavaScript coding style guide and stick to it

Even though you can set up your own coding guidelines that fit with your goals, you don’t necessarily have to. There are multiple ready-to-use JavaScript coding style guides that are well tested and widely used across the industry. Some examples include the community-based Idiomatic JavaScript, the Google JavaScript Style Guide, and AirBnB’s JavaScript Style Guide.

Using a coding style guide is especially important in teamwork, where multiple developers are working on the same project. As Idiomatic JavaScript puts it, “all code in any code base should look like a single person typed it, no matter how many people contributed.” Whether you create your own style guide or use a third-party one, consistent code will be easier to read, write, debug, and maintain.

2. Give descriptive names to variables and functions

Make the names of your variables, functions, and other code structures easy to understand for anyone who works with your code. Don’t use names that are either too short or too long, and take care that they succinctly describe their own purpose.

Some good examples are (from the DOM API):

  • Properties (variables): firstChild, isConnected, nextSibling
  • Methods (functions): getResponseHeader(), addEventListener()
  • Objects: XMLHttpRequest, NodeList
  • Interfaces: HTMLCollection, EventTarget

Also keep in mind that JavaScript is a case-sensitive language. As camel case capitalization is the most common naming system in JavaScript programming, you should use lower camelcase for variables and functions and upper camelcase for classes and interfaces for better code readability.

3. Use shorthands, but be careful with them

JavaScript includes many shorthands that allow you to write code faster and make your scripts load faster.

There are older shorthands that have been part of the JavaScript syntax for ages, such as the if true shorthand:

/* Longhand )*/
if (value = true) {  }

/* Shorthand */
if (value) {  }

And of course, there are newer ones, too, such as arrow functions introduced in ECMAScript 6:

// Longhand
let add = function(a, b) {
	return a + b;
}

// Shorthand
let add = (a,b) => a + b;

Sometimes, however, shorthands might return surprising results. So, always be sure of what you’re doing, check the documentation, find relevant JavaScript code examples, and test the outcome.

4. Comment your code, but keep it short

Commenting is a hot topic in programming. Some say it’s not necessary at all, but code without any comments gets hard to read after a while — especially when working on team or legacy projects. On the other hand, overly long and verbose comments are unnecessary because they’ll take extra time to read through and understand.

As a rule of thumb, comment your code, especially functions, classes, objects, and interfaces, but only include key information. To keep your comments concise and easy to read, use tags such as @constructor, @param, @version, @return, and others.

Here’s an example of a short but descriptive comment from JSDoc’s guidelines:

/**
* Represents a book.
* @constructor
* @param {string} title - The title of the book.
* @param {string} author - The author of the book.
*/
function Book(title, author) {  }

If you comment your JavaScript code properly, you can also use a tool such as JSDoc to automatically generate documentation for your project.

JavaScript coding practices

5. Avoid using JavaScript to add styling

Taking separation of concerns (SoC) seriously is one of the most important JavaScript coding practices. Technically, JavaScript allows you to directly change the CSS code using the style property. However, to make debugging easier and improve code maintainability, it’s best to avoid it.

As an alternative, you can add and remove classes using the ClassList API as follows (then add the style rules with CSS):

// selects the first p tag on the page
let p = document.querySelector("p");

// dynamically adds classes
p.classList.add("class-1", "class-2");

// dynamically removes classes
p.classList.remove("class-3", "class-4");

// based on the presence of the class, it’s either added or removed
p.classList.toggle("class-5");

6. Declare local variables with let and const

As global variables are available to all the scripts running on your page, they can easily be overridden by other variables using the same name. There are various ways to reduce the number of global variables, but ECMAScript 6 has introduced two new JavaScript statements you can use to declare variables in the local scope: let and const.

Both let and const create block-scoped local variables, but let can be re-declared (in a different scope) while const can’t. As opposed to var, neither let nor const becomes a property of the window object, so they can’t be accessed, and therefore overridden, by other scripts — even when they are declared globally (in the highest scope).

You can define let with or without a value (however, it can only be read and written after full initialization), but you can’t declare const without a value:

let localVar;
let localVarText = "text string";

const LOCAL_CONST = 999;

7. Use for...of instead of for loops

Loops can get costly performance-wise because you repeat the same operation over and over again. However, if you optimize them, you can make them run faster.

There are many JavaScript best practices to write more performant loops, such as avoiding nesting, keeping DOM manipulation outside of loops, and declaring a separate variable for the length of the loop (e.g. let cityCount = cities.length).

Using the for...of statement instead of for is such a JavaScript coding practice, too. This syntax has been introduced by ECMAScript 6, and it includes a built-in iterator so that you don’t have to define the i variable and the length value:

// with for loop
let cities = ["New York", "Paris", "Beijing", "Sao Paulo", "Auckland"];
let cityCount = cities.length;

for(let i = 0; i < cityCount; i++) {
	console.log( cities[i] );
}

// with for...of loop
let cities = ["New York", "Paris", "Beijing", "Sao Paulo", "Auckland"];

for(city of cities) {
	console.log(city);
}

You can use the for...of loop to iterate over any iterable object, such as arrays, strings, nodelists, maps, and more.

8. Create helper functions for common tasks

The topic of modularization comes up frequently in discussions of JavaScript best practices. Modularization means the splitting of larger files into smaller parts — a.k.a. reusable modules. ECMAScript modules and module bundlers such as Webpack help with setting up such architecture, but creating modular and reusable functions is just as important.

As the first SOLID design principle (single responsibility principle) states, every function, interface, class, and other code structure should perform only one task. Creating helper functions for common tasks is one way to follow this principle. Helper functions should be context-independent so that they can be called from any module.

To see some JavaScript functionality examples, check out The Vanilla JS Toolkit that includes smart helper functions such as animate(), getParents(), pick(), shuffle(), and others. For example, here’s the pick() function — as you can see, it performs one single action:

function pick (obj, props) {

	// Create new object
	var picked = {};

	// Loop through props and push to new object
	for (let prop of props) {
		picked[prop] = obj[prop];
	}

	// Return new object
	return picked;
}

JavaScript classes best practices

9. Avoid creating irrelevant classes

Using classes is another hot topic in JavaScript programming. Even though JavaScript has a prototypical inheritance model, ECMAScript 6 has introduced the class syntax to improve code readability. It’s frequently referred to as “syntactic sugar” as it doesn’t add any new functionality, and, as stated by the MDN Docs, classes are “being converted into Prototypal Inheritance models” under the hood.

So whether you use classes or not mainly depends on your preferences and project requirements — but if you do decide to use the class syntax, there are, of course, some JavaScript best practices to follow.

First and foremost, you need to avoid creating irrelevant classes to build a well-structured application. Classes can be irrelevant for a couple of reasons, including:

  • Catch-all classes that do more than one thing (or everything)
  • Classes that only have data (properties) but no actions (methods) – try to move the data into another class
  • Classes that only include behavior (methods) but no data (properties) – in this case, it’s better to create a function

Overall, classes should respect the single responsibility principle (similar to functions) and include both data and behavior, for example:

class Product {
	constructor(name, price) {
		this.name = name;
		this.price = price;
	}
	displayInfo() {
		console.log('Hi, you can see a ' + this.name + ' that costs $' + this.price);
	}
}

10. Pay attention to (the lack of) hoisting when using classes

In JavaScript, classes are a special type of function, but they don’t always behave like ordinary functions, which can lead to some confusion.

For instance, unlike functions, classes are not hoisted. Hoisting means that the JavaScript interpreter moves function declarations to the top of their scope so that they can be called before being declared. When working with classes, you can’t take advantage of this feature, however.

You always need to declare a class before you call it, otherwise you’ll get an uncaught reference error. Following the previous example with the Product class:

// uncaught reference error
const hat = new Product("red hat", 1000);
hat.displayInfo();
class Product {  }

// good order - the class is declared first
class Product {  }
const hat = new Product("red hat", 1000);
hat.displayInfo();

Next steps

The JavaScript coding practices discussed above can help you write cleaner and better-structured code that’s easier to maintain and debug. However, creating development code is just the first step of the software life cycle.

Even if you’re diligently following these JavaScript best practices, errors can (and will) still occur when your end-users interact with your application. To ensure your users experience your code as intended, you need to detect and address errors in production. With JavaScript error tracking, you can take care of errors on the production side, monitoring your application in real-time, catching errors as they happen, and taking immediate action.