Stack Overflow developer survey 2023 just dropped, and for the eleventh year in a row, JavaScript holds the top spot of the most commonly-used programming language. It’s a breathtaking achievement, but it’s hard to say that no one expected this. Statistics show that it is used on 98.7% of websites as a client-side technology nowadays. Moreover, it can take care of the server-side code as well, and by the way, Node.js ranks first among the most common web frameworks and technologies in the same survey.

Considering this, there’s no wonder the number of JavaScript enthusiasts grows daily. However, mastering your craft as a JavaScript developer involves understanding things that lay beyond basic syntax. Today, we’ll share some JavaScript concepts that can become a solid foundation for your future success.

Hoisting

Hoisting is a concept in JavaScript that can sometimes lead to unexpected results if developers are unfamiliar with it. It refers to the behavior where the JavaScript interpreter moves variable and function declarations to the top of their respective scopes (either function scope or global scope) before executing the code. As a result, you can call a function or access a variable before it is actually defined in the JavaScript code without encountering an ‘Uncaught ReferenceError.’

Let’s delve into this concept with an example:

In this case, we call hoistedFunction() before its actual definition in the code. Surprisingly, it works without any errors due to hoisting, where the function declaration is moved to the top of the current scope by the JavaScript interpreter.

However, it’s important to note that, in JavaScript, only the declarations are hoisted, not the initializations or assignments. Here’s another example:

In this scenario, a variable hoistedVariable is accessed before its declaration. The first console.log() statement outputs undefined because the variable is hoisted to the top, but its value is not assigned yet. The second console.log() statement in this code outputs the assigned value “I am hoisted” after the variable is initialized.

Here’s a more complex example that uses hoisting to create a simple router for a web application:

Here, the function declarations for router, home, about, contact, and notFound are defined before invoking the router function. This allows the functions to be hoisted to the top of their respective scopes, and they can be invoked before their actual definitions in the code.

Understanding hoisting helps developers write clear and maintainable code. While JavaScript hoists the declarations, it’s considered a best practice to declare variables and functions at the top of their respective scopes to avoid confusion and potential bugs.

Additionally, it’s worth noting that in JavaScript, let and const declarations are also hoisted, but unlike var declarations, they are not initialized with a default value of undefined. This behavior is known as the “temporal dead zone” and can lead to ReferenceErrors if developers try to access variables before their declarations in block scopes.

Closures

In JavaScript, closures are defined within other functions, allowing them to access variables from the outer function’s scope. While the concept may seem simple, closures possess great power when developers write code that interacts with different scopes.

Read Also Top JavaScript Frameworks: From Industry Titans to Modest Hard Workers

The inner function, often referred to as the closure, can access variables from its own scope (between its curly brackets), as well as variables from its parent’s scope and global variables. It’s important to note that, in JavaScript, the outer function cannot access the variables defined within the inner one:

In this JavaScript code, a function called makeAdder has parameter x, returns an inner function that takes another parameter y, and returns the sum of x and y. In this case, an inner function has access to variables defined in its outer function, even after the outer one has finished executing.

A more complex code example below shows how closures can be used to create a simple authentication system for a web application. Here an inner function (login and logout) retains access to variables from its outer function called auth even after the outer function has finished executing:

Using Shorthands in JavaScript

Shorthands, in general, and arrow functions, in particular, provide developers with a concise syntax for making JavaScript code shorter and more readable:

In the above JavaScript code, both functions work the same way. However, you can see that the arrow function has a cleaner and shorter syntax. The empty parentheses () in the arrow function’s syntax represent the arguments. Even if there are no arguments, these parentheses should be present.

Additionally, in JavaScript, if there is only one argument, developers can skip the parentheses altogether, like this:

Moreover, for one-liner arrow functions, you can skip the return statement. The expression after the arrow is automatically returned:

In this JavaScript code, the arrow function double takes a number as an argument and returns its double without explicitly using the return keyword.

For more complex logic or multiple statements, developers can declare a multiline arrow function in their JavaScript code using curly braces {} similar to regular functions:

It’s important to note that arrow functions have a lexical this binding, meaning they inherit the this value from the enclosing scope. This behavior differs from regular functions, which have their own this value that is determined by the way they are called in the code. Described JavaScript functionality can be especially useful when developers work with higher-order functions, such as map, filter, and reduce, where the concise syntax is often preferred.

Destructuring

Destructuring in JavaScript allows developers to extract values from arrays or objects and assign them to variables concisely and flexibly. It provides a convenient way to unpack data structures and access their elements easily.

With array destructuring, developers can write JavaScript to extract values from an array and assign them to variables in a single line of code. The pattern of the destructuring assignment resembles the structure of the array:

In this JavaScript code, we have an array named colors with three elements. By using array destructuring, we assign the values of the array to the variables firstColor, secondColor, and thirdColor. Each variable receives the corresponding value from the array.

Destructuring also allows JavaScript developers to extract values from an object and assign them to variables with the same names as the object’s properties:

And here’s how we can use destructuring to extract data from an API response and display it on a web page. Here, the fetch function returns a promise that resolves to a response object:

Rest and Spread Operator

In JavaScript, developers use three dots for both rest and spread operators. However, they work differently.

The rest operator can be used in JavaScript code to gather multiple elements into an array. It allows you to represent an indefinite number of arguments as an array within a function or when destructuring arrays. The rest operator collects the remaining elements into a new array:

In the above JavaScript code example, the sum function uses the rest operator …numbers in its parameter list. It allows sum to accept any number of arguments and gather them into an array named numbers. The reduce method is then used to calculate the sum of all the numbers.

Read Also The Best Time to Start Learning is Now. Five Full-stack Projects to Improve Your Skills in 2023

The spread, in its turn, is used to spread the elements of an array or object into individual elements. It is particularly useful when developers want to write JavaScript code for combining or cloning arrays, passing multiple arguments to functions, or creating new arrays or objects:

Here, the spread operators …numbers and …moreNumbers are used within an array literal. This code spreads the elements of the numbers array and the moreNumbers array, combining them into a new array called combined.

You can use these operators, for example, for creating a simple shopping cart system for a web application. In the example below, the rest operator is used to collect arguments into an array in the add method, and the spread operator is used to expand an array into individual elements when adding products to the items array:

Promises

Promises are a powerful tool in modern JavaScript that allows developers to deal with the outcome of asynchronous operations, which can either be successful (resolved) or encounter an error (rejected) in JavaScript terminology.

A promise in JavaScript can be in one of three states:

  1. Pending is the initial state when the final result is yet to be determined. The asynchronous operation is still in progress;
  2. Resolved, aka fulfilled, signifies that the promise has been successfully resolved with a result. The asynchronous operation has completed successfully, and the promise holds the resolved value;
  3. Rejected shows that the promise has encountered an error or has been explicitly rejected. The asynchronous operation has failed, and the promise contains the reason for the rejection.

Developers can utilize the .then(), .catch(), or finally() methods to handle the outcome of operations:

  1. .then() is called when a promise is either resolved or rejected. It takes two callback functions as arguments. The first callback is executed after the successful resolution, receiving the resolved value as its argument. The second callback is optional and, in case of rejection, allows JavaScript developers to handle the error condition;
  2. .catch() is specifically used in JavaScript as an error handler and is called in case of rejection or error during code execution. It provides developers with a convenient way to handle and process errors;
  3. .finally() allows attaching a callback function to a promise that will be executed regardless of whether it is fulfilled or rejected. In other words, it lets you specify a piece of JavaScript code that should be executed after a promise settles, regardless of its outcome.
In this example, we define a function fetchData() that returns a promise. We simulate an asynchronous operation inside the constructor using setTimeout(). If the operation succeeds, we call resolve() with the desired result. If not, we call reject() with an error object.

This feature provides developers a structured way to handle asynchronous operations and make JavaScript code more readable and maintainable. It plays a crucial role in modern JavaScript development, especially when working with APIs, making network requests, or dealing with time-consuming tasks.

In a more complex example, we’ll use promises to create a simple image loader for a web application. Here, the loadImage function takes a URL as an argument and returns a promise that resolves to an image element. Your homework for today is to add .catch to the second loadImage() call:

Async/await

The async/await functionality provides a more elegant and readable approach to working with Promises. While JavaScript is inherently synchronous, async/await allows us to write promise-based code in a way that appears synchronous, pausing the execution of further operations when needed.

Read Also The Difference Between Asynchronous and Synchronous Programming

To use async/await, JavaScript developers can prefix a function declaration with the magic word async. For example:

By adding async before a function you show that it will always return a Promise. It allows you to use the await keyword within the function to pause the execution of code until it is resolved or rejected. Note that you can only use await inside an async function:

Here, inside the async function called asyncFunction(), we create a new promise and immediately resolve it with the value. Thanks to the await keyword in front of it, the execution of the code is paused until it’s resolved. After it’s resolved, the value is assigned to the response variable, and we log it to the console.

Async/await provides JavaScript developers a more linear way to handle asynchronous operations, making the code more intuitive. Under the hood, async functions still return promises, allowing developers to leverage their benefits, such as error handling using try/catch blocks. You may want to use async with all your functions, however, it won’t make much sense without using await inside them.

As a practical example, let’s see the code that uses async/await to create a simple currency converter for a web application. The convert function is declared as async and takes two arguments: the amount and the currency. The function returns a promise that resolves to the converted amount:

Conclusions

These seven tips we gave you today can serve as a trampoline on your path toward the peaks of skill. Re-read them twice a day until you remember them and move forward, learning more complex concepts the world of JavaScript programming has to offer. It’s constantly evolving, and no one knows what’s waiting around the corner. No one, except our developers. They always keep their fingers on the pulse of life to ensure their skill set corresponds to the needs of the most demanding customers.

Contact us if you’re looking for an experienced JavaScript team.