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.
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.
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.
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.
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:
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.
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.
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.
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.
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.
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:
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:
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.
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:
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.
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.
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.
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.
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.
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.
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.