8 Javascript best practices with examples

13 min read

In this article we will cover 8 best practices for writing clean and performant Javascript code with examples. Mastering these practices will help you write cleaner and maintainable code and therefore make everyone touching the code you wrote, a little happier.

lightbulb

Intended audience

This tutorial is intended for anyone who wants to learn or Javascript best practices regardless of their skill level. Works as a great stepping stone for beginners and a refresher for more experienced developers.

1. Use const and let instead of var

The var keyword is function scoped and not block scoped. This means that a variable declared with var is available anywhere in the function it is declared in. This can lead to unexpected behavior and bugs in your code.

In this example, the variable message is declared inside the if block but is still accessible outside of it. This is because variables declared with var are scoped to the nearest function, rather than being block-scoped. So, within the exampleFunction, the message variable is available both inside and outside of the if block, which may lead to unexpected behavior and bugs.


The let keyword is block scoped and only available in the block it is declared in. This makes it easier to reason about your code and prevents bugs.


Below code demonstrates the use of let with the same example as above:

With let, the message variable is only accessible within the if block, resulting in a ReferenceError when trying to access it outside of the block. This behavior helps prevent unintended variable reuse and promotes cleaner code.


The const keyword is also block scoped and only available in the block it is declared in. The difference between let and const is that const variables cannot be reassigned.

In this example, the message variable is declared with const inside the if block. As a result, it is only accessible within that block. If you try to access it outside the block, it will result in a ReferenceError as shown in the commented line.


Furthermore, the const keyword ensures that the variable cannot be reassigned. If you uncomment the line message = 'New message';, it will result in a TypeError because you're attempting to modify a constant value. This immutability feature of const provides guarantees that the variable's value remains constant throughout its scope, helping to prevent accidental reassignments.


Remember, when using const, it's important to note that it enforces immutability of the variable itself, not necessarily the value it holds. For example, if message was an object, you could still modify the properties of that object. However, you wouldn't be able to reassign message to a new object entirely.

2. Use arrow functions

Arrow functions are a more concise way of writing functions. They are anonymous* and do not bind their own this*, arguments, super*, or new.target*. This makes arrow functions especially useful for callbacks and inline functions.


Here's an example:

In the example above, we have a regular function regularFunction and an arrow function arrowFunction. The arrow function is a more concise way of writing a function, as it omits the function keyword and uses the => arrow syntax.

Arrow functions are particularly useful for callbacks and inline functions because they inherit the this value from the surrounding context, instead of having their own this binding. This behavior is known as lexical scoping, which means that the value of this inside an arrow function is determined by the context in which the arrow function is defined, rather than how it is called.


Here's an example that illustrates the lexical scoping of arrow functions:

In this example, obj has two functions: regularFunction and arrowFunction. Inside the regularFunction, this refers to the obj object, so it can access obj.name. However, inside the arrowFunction, this does not have its own binding and instead inherits the value of this from the surrounding context (which is the global object in this case), resulting in undefined for this.name.


Arrow functions also do not have their own arguments object, super keyword, or new.target binding. Instead, they rely on the lexical scope of their surrounding context for these values.


Overall, arrow functions offer a concise syntax and lexical scoping behavior, making them well-suited for certain use cases like callbacks and inline functions.

3. Use template literals instead of concatenation

Template literals are a more concise way of concatenating strings. They allow you to embed expressions inside a string using the ${} syntax. This makes it easier to write and read strings with dynamic content.


Here's an example:

4. Use default parameters

Default parameters allow you to specify default values for function parameters. This makes it easier to write functions that accept optional arguments.

In this example, we have a function greet that accepts a name parameter. If no argument is passed for name, it will default to 'John'. This makes it easier to write functions that accept optional arguments.

5. Use the spread operator

The spread operator in JavaScript is denoted by three dots (...). It allows you to expand or spread iterable objects (such as arrays, strings, or objects with an iterator) into individual elements. The spread operator is commonly used in various scenarios, including array manipulation, function arguments, and object merging.

Array manipulation

The spread operator can be used to create a new array by combining or cloning existing arrays. It allows you to concatenate arrays or add new elements to an array.

Function arguments

The spread operator can be used to pass an array as separate arguments to a function. This is particularly useful when you have an array of values that need to be passed into a function as individual arguments.

Merging objects

The spread operator can be used to create a new object by merging properties from existing objects. It makes it easy to clone an object or combine properties from multiple objects into a single object.

6. Use object and array destructuring

Object and array destructuring allow you to extract values from objects and arrays into individual variables. This makes it easier to access object properties and array elements.


Object destructuring:

Array destructuring:

7. Use shorthand property names

Shorthand property names allow you to create objects without explicitly specifying property names. This makes it easier to write and read object literals.


Here's an example:

8. Use the for...of statement

The for...of statement allows you to iterate over iterable objects (such as arrays, strings, or objects with an iterator) and execute a block of code for each element. It is similar to the for...in statement, but it does not iterate over object properties.


Here's an example:

In this example, we have an array of numbers. The regular for loop iterates over the array and logs each number to the console. The for...of loop does the same thing, but it is more concise and easier to read as it reads like english.

Bonus

Nice!

Good job - you've reached the end of the 8 Javascript best practices with examples. We covered a lot of technical direct in-code best practices, but there are also some best practices that are not directly related to code and I've listed a few:

  1. Consistent Code Formatting: Discuss the importance of consistent code formatting, including the use of indentation, line length, spacing, and naming conventions. Emphasize the use of tools like ESLint to enforce consistent formatting.

  2. Naming conventions: Explain the significance of clear and meaningful variable and function names. Encourage developers to use descriptive names that accurately represent the purpose or behavior of the entity.

  3. Magic Numbers and Strings: Highlight the importance of avoiding hardcoded values (magic numbers and strings) in code. Encourage the use of constants or variables with descriptive names to improve code readability and maintainability.

  4. Commenting and Documentation: Stress the importance of adding comments where needed* to code to explain complex logic, assumptions, or potential pitfalls. Encourage developers to document code, especially public APIs, to make it easier for others to understand and use. Dont over-use comments.

  5. Separation of Concerns: Discuss the concept of separation of concerns, which involves dividing code into distinct modules or functions that handle specific tasks. Explain how this improves code maintainability, reusability, and testability.

  6. Error Handling: Explain the importance of proper error handling to ensure that unexpected situations are gracefully handled and appropriate feedback is provided to users. Discuss best practices such as using try-catch blocks and providing meaningful error messages.

  7. Testing and Test-Driven Development (TDD):* Highlight the importance of writing automated tests to ensure code correctness and prevent regressions. Discuss the benefits of (TDD) and suggest tools and frameworks for writing unit tests in JavaScript.