Understanding Arrow Function Expressions in JavaScript

Understanding Arrow Function Expressions in JavaScript

According to recent developer surveys, JavaScript is still the most commonly-used programming language in the world and it remains in high demand by various organizations across the globe. In light of that piece of information coupled with the fact that I love writing about JavaScript, I'll be dedicating more time and resources to write up quality content about the language. The topic with which we are now concerned is; Arrow Functions. Without wasting any more time, let's start looking at the ins and outs of Arrow Functions. But before we do that, I'll like us to have a little refresher on regular or traditional JS functions so that it puts everything into perspective. Shall we?

Functions are one of the fundamental building blocks in JavaScript and they are basically blocks of code designed to perform particular tasks. To use a function, you must first define it somewhere in the scope from which you wish to call it.

Functions are defined, or declared, with the function keyword. Below is the syntax for a function in JavaScript.

function nameOfFunction() {
    // Block of code to be executed
}

The declaration begins with the function keyword, followed by the name of the function. Function declarations load into the execution context before any code runs. This is known as hoisting, meaning you can use the function before you declare it.

Another way of defining a function is by means of a function expression which is basically assigning a function to a variable. Unlike a function declaration, function expressions are not pre-loaded into the execution context, and only run when the programme encounters it. Below is an example of a function expression that computes the sum of two numbers.

const sum = function (a, b) {
  return a + b
}

Trying to call this function before defining it will result in an error. Now unto arrow functions!

Arrow functions

An arrow function is fundamentally a shorter alternative to traditional JS function expressions with a few semantic differences and a deliberate limitation in their usage. Arrow functions are always anonymous—there is no way to name an arrow function. In the next section, you will explore the syntactical and practical differences between arrow functions and traditional JavaScript functions.

Behaviour and Syntax of Arrow Functions

Arrow functions have a few important distinctions in how they work that make them different from traditional JS functions, as well as a few syntactic enhancements. These differences are:

  • Arrow functions don't have their own bindings to this

  • Arrow Functions Have No constructor or prototype

Now let us look at what these points mean in detail.

Arrow functions don't have their own bindings to this: The context inside arrow functions is lexically or statically defined. What this means is that, unlike regular JS functions, the value of this inside arrow functions is not dependent on how they are called or how they are defined. It depends only on its enclosing context. This might seem a little bit confusing so let's try to understand it by looking at an example. In this example, we will create a basic object and see the use of the object in Javascript.

let Student = function(student, age) {
        this.student = student;
        this.age = age;
        this.info = function() {

         // logs Student
         console.log(this);

         setTimeout(function() {
            // here this!=Student
           console.log(this.student + " is " + this.age + 
                                              " years old");
          }, 2000);
        }
    } 
 let student1 = new Student('James', 19);

    // logs : undefined is undefined years old after 3 seconds
    student1.info();

The reason that we get undefined values outputted instead of the proper info is because the function() defined as the callback for setTimeout() is a traditional JS function and this means that its context is set to the global context or in other words the value of this is set to the window object.
This happens because every regular JavaScript function defines its own this or context depending on their invocation. The context of the enclosing objects or function does not affect this tendency to automatically define their own context.

Now how do we solve this issue? One obvious solution that might come to mind is, what if the function did not define its own this or context? What if it inherited the context from info(), because that would mean the callback function gets the this as was defined in info()
Well, that is exactly what arrow functions do. They retain the value of this from their enclosing context. In the above example, if the callback function in setTimeout() were an arrow function it would inherit the value of this from its enclosing context – info()

let Student = function(student, age) {
        this.student = student;
        this.age = age;
        this.info = function() {

            // logs Student
            console.log(this); 

           setTimeout(() => { 
            // arrow function to make lexical "this" binding
            // here this=Student."this" has been inherited
            console.log(this.student + " is " + this.age 
                                           + " years old");
           }, 2000);
        }
    } 
    let student1 = new Student('James', 19);

    // logs : James is 19 years old after 2 seconds
    student1.info();

An arrow function’s this value is the same as the value of this in its enclosing context that is the context immediately outside it. If used outside any enclosing function, an arrow function inherits the global context, thereby setting the value of this to the global window object.

Arrow Functions Have No constructor or prototype : In JavaScript, functions and classes have a prototype property, which is what it uses as a blueprint for cloning and inheritance. To demonstrate this, create a function and log the automatically assigned prototype property to the console:

function myFunction() {
  this.number = 8
}

// Log the prototype property of myFunction
console.log(myFunction.prototype)

This will print the following to the console:

Output
{constructor: ƒ}

This shows that in the prototype property there is an object with a constructor. This allows you to use the new keyword to create an instance of the function:

const newInstance = new myFunction()

console.log(newInstance.value)

This will give the value of the number property that you defined when you first declared the function. In contrast, arrow functions do not have a prototype property. Create an arrow function and try to log its prototype to the console, you'll see that the output you get is undefined . As a result of the missing prototype property, the new keyword is not available and you cannot construct an instance of the arrow function.

In addition to these differences that arrow functions exhibit, there are a few optional syntactic changes that make writing arrow functions quicker and less verbose. The next section will show examples of these syntax changes.

Arrow Function Implicit Return

It is common knowledge that the body of a traditional JavaScript function is contained within a block using curly brackets {} and ends when the code encounters a return keyword. The following is what this implementation looks like as an arrow function:

const sum = (x, y) => {
  return x + y
}

Arrow functions also allow for an omission of the return keyword and curly brackets altogether. When this happens, there is an implicit return.

const sum = (a, b) => a + b

Implicit return is useful for creating succinct one-line operations in array methods such as map , filter and other common array methods. Note that both the brackets and the return keyword must be omitted. If you cannot write the body as a one-line return statement, then you will have to use the normal block body syntax.

In a case where an object is returned, the syntax requires that you wrap the object literal in parentheses as shown in the example below:

const sum = (a, b) => ({result: a + b})

sum(1, 2)

If you don't enclose the object literal in parentheses, the brackets will be treated as a function body and will not compute a return value.

Omitting Parentheses Around a Single Parameter

Another useful syntactic improvement in Arrow functions is omitting the parentheses when you have just a single argument. Check the example below:

const square = (x) => x * x

Note: If your Arrow function has no arguments you should not leave out the parentheses because they are required.

Conclusion

Always try to remember the differences between regular JS functions and Arrow functions. As we have just seen, they might behave differently in the same situations. It is good practice to use arrow functions for callbacks and closures because their syntax is concise and cleaner.