Arrow Functions – When and When not to use them ?

Hey everyone 👋🏻,
In this article, let us understand When and when not should we use arrow functions.

Arrow Functions

With ES6, JavaScript has evolved a lot because of the customizations that the new standard brings to the language. It has immensely improved our development experienced with the introduction of Next Generation JavaScript features. One of such features that ES6 brings to the table is the Arrow Functions.
The purpose of writing this article is to make the reader aware about the scenarios where he/she should not use arrow functions and instead make use of the good old school function syntax or the function expression syntax. We need to be a bit careful as well while using arrow functions because they can affect the readability of our code as well.

When to use Arrow Functions ?

There are some solid reasons why using arrow functions can be a plus.

  1. Securing the scope : The arrow functions remember their context in which they are defined in and they stick to that.
    So even if we introduce a normal callback function and mix that with a couple of arrow functions, then that can lead to messing up of the scope.
  2. Compact : Easier to read and write. Just a fat arrow, and they can be anonymous as well.
  3. Code Clarity: When every function in your code is an arrow function, then a normal function is the one that we rely on for defining the scope. So we as developers can always look up the next higher order function statement to figure out the this keyword.

1. Defining methods on an object

In JavaScript, as we know a method is a function that we set in a property of the object. So right at the time of invoking the method, the this keyword points to the object that the method belongs to.

Now let us talk about Object Literals here. Let us say we want to define a method inside an object literal. At first, you might be tempted to use an arrow function for a method definition within the object literal. But let us understand with the help of an example, why you should not use an arrow function within the object literals.

const user = { 
  stocks : [
     {
      stock : 'Amazon', 
      price_in_dollars : 937,
      units_bought: 5
   },
   {
      stock : 'Tesla', 
      price_in_dollars : 652,
      units_bought: 3
   }, 
   {
      stock : 'Meta Platforms', 
      price_in_dollars : 465,
      units_bought: 2
   }
],
get_total_invested : () => { 
        console.log(this === window);
        return this.stocks.reduce((acc, stock) => acc + stock.price_in_dollars * stock.units_bought, 0);
  }
};

console.log(this === window);
const invested_amount = user.get_total_invested(); 
console.log(invested_amount); 

user.get_total_invested method as we can see is defined within an arrow function. But if you try the above code, the invocation to user.get_total_invested would throw a TypeError because in the above case this.stocks would resolved to undefined.

dns7pu8ngadq8io1s1wk-4893115

Now the thing to note here is that when we are invoking the get_total_invested method on the user object, the context in which it is called in is the window object which we can easily verify by comparing this keyword with the window object in the get_total_invested method as you can see within this method, the this keyword points to the window object. So in essence, this.stocks resolves to window.stocks and this ultimately gives us undefined because we don’t have any stocks property on the window object.

In order to solve this, we can simply switch to the function expression syntax or its shorthand for method definition. For this case, at the invocation point, this keyword would refer to the the thing to the left of the dot at the invocation point. Here is the code with the above modifications :

const user = { 
  stocks : [
     {
      stock : 'Amazon', 
      price_in_dollars : 937,
      units_bought: 5
   },
   {
      stock : 'Tesla', 
      price_in_dollars : 652,
      units_bought: 3
   }, 
   {
      stock : 'Meta Platforms', 
      price_in_dollars : 465,
      units_bought: 2
   }
],
get_total_invested(){ 
        console.log(this === user);
        return this.stocks.reduce((acc, stock) => acc + stock.price_in_dollars * stock.units_bought, 0);
  }
};

const invested_amount = user.get_total_invested(); 
console.log(invested_amount);

Now in the above code, get_total_invested is a method that is being called on the user object. So with the regular function syntax, the thing to the left of dot which is the user object here is what this resolves to in the end and therefore the above code would work fine. Here is the output for the above code :

fz33s7sjekhr0uxgxk6d-2799209

From the output, you can also easily decipher that the this keyword indeed points to the user object and hence this === user returns true as a result.

Let us now move forward and take arrow functions with Object Prototype

function User(name, age, profession) { 
  this.name = name; 
  this.age = age; 
  this.profession = profession;
}
User.prototype.sayHello = () => { 
  console.log(this === window);
  return `Hello ${this.name}`; 
}
const user = new User('Alex',24,'Developer');
console.log(user.sayHello());

Now here also the same thing holds true as discussed above. In the above code, instead of using the arrow function syntax for defining our sayHello method where the this keyword points to window we should instead use the function expression syntax to solve this. So here is the modified code :

function User(name, age, profession) { 
  this.name = name; 
  this.age = age; 
  this.profession = profession;
}
User.prototype.sayHello = function(){ 
  console.log(this === window);
  return `Hello ${this.name}`; 
}
const user = new User('Alex',24,'Developer');
console.log(user.sayHello());

So with just this simple change, sayHello regular function is changing its context to user object when called as a method : user.sayHello().

2. Arrow Functions cannot be used as constructors

For constructors, use regular functions otherwise JavaScript will throw an error if it encounters an arrow function getting used as a constructor. This means that JavaScript implicitly prevents from doing that by throwing an exception.

Let us go a bit deeper inside and try to understand a bit more on this :

In simple words, the objects (or the “constructors”) rely on the this keyword to be able to be able to be modified.

The this keyword always references the global object which is the window object.

So if we have something like this :

const User = (name, age, profession) => { 
    this.name = name; 
    this.age = age;
    this.profession = profession; 
}
const user = new User("Alex", 21, "developer"); 

Executing new User("Alex", 21, "developer"), where User is an arrow function, JavaScript throws a TypeError that User cannot be used as a constructor.

But, instead if you do something like this :

const User = function(name, age, profession) {
   this.name = name;
   this.age = age;
   this.profession = profession; 
}
const user = new User('Alex',21,'developer');
console.log(user.name);

Now here, the new keyword does some magic under the hood and makes the this keyword that is inside the User to initially first refer to the empty object instead of referencing to the global object like it earlier did. Right after this, new properties like name,age and gender get created inside this empty object pointed to by the this keyword and the values will be Alex,21 and developer respectively. Finally, the object pointed to by the this keyword gets returned.

Now a thing to note here is that arrow functions do not allow their this object to be modified. Their this object is always the one from the scope where they were statically created. This is called Static Lexical Scope. This is precisely the reason that why we cannot do operations like bindapply, or call with arrow functions. Simply, their this is locked to the value of the this of the scope where they were created.

3. Arrow Functions as callback functions

As we know in client side JavaScript, attaching event listeners to the DOM elements is a very common task. An event triggers the handler function with this as the target element.

So for example, let us say we have this code 

const button = document.querySelector('button');
button.addEventListener('click',() => { 
 console.log(this === window); 
 this.innerHTML = 'Changed';
});

Now as discussed just a while back, this points to the window object in an arrow function that is defined in the global context. So whenever a click event occurs, browser tries to execute the handler function with the button as context, but arrow functions still stays bound to its pre-defined context.
In above code, this.innerHTML is same as window.innerHTML which does nothing infact.

In order to solve this, we need to make use of the function expression syntax which will allow us to change this depending on the target element.

const button = document.querySelector('button');
button.addEventListener('click', function() { 
 console.log(this === button); 
 this.innerHTML = 'Changed';
});

Now for the above code, this keyword will infact point to the button. Therefore, this.innerHTML will correctly modify the button text and reflect the same in the UI.

So this is it for this article. Thanks for reading.

Previous Post
Next Post