Categories
javascript

Arrow Functions vs Normal Functions

Hey everyone ūüĎčūüŹĽ,
In this article, let us understand the differences between Arrow Functions and Normal Functions.

Arrow Functions vs Normal Functions

We know that we can define JavaScript functions in many ways:

  • The first way is by making use of the function keyword.

So as an example, consider this piece of code :

function greet(name) { 
  return `Hey ${name}`;
}

This comes under normal functions that we declare when we write code. Another syntax that we often see getting used in the code is Function Expression Syntax

So let us transform the above function declaration into a function expression :

const greet = function(name) { 
  return `Hey ${name}`; 
}
  • The second way is using the arrow function syntax :

So let us transform the above code based on function expression to a code that uses the arrow function syntax

const greet = name => {
  return `Hey ${name}`;
}

Now if you see any of the above syntaxes can be used for defining functions, what you choose depends on you but knowing the differences between these syntaxes will make it easier to decide regarding the choice of syntax in different cases.

 this keyword

this keyword as we know is a property of an execution context. The main use of it is observed in functions and constructors. Here is a video where you can learn about the rules of the this keyword :

https://youtu.be/O5aShSPaMbE

  • Normal functions

this inside a normal function is dynamic. By dynamic, it means that the value of this depends on how we invoke the function. Let us see the ways we can invoke a normal function.
So during a normal invocation, the value of this equals to the global object. There is a small exception to this : If the function runs in strict mode, then the this keyword equals undefined. So let us see an example :

function someFunction() { 
   console.log(this);
}
someFunction(); 

The above examples logs the window object to the console.

Next, if we have a method that is sitting within an object, then the this keyword when referenced in the scope of method will point to the object that owns that particular methods which also represents the context in which that method is defined in.

So let us see an example for this as well :

const someObject =  { 
   greet() { 
        console.log(this);
        console.log('hello');
   }
};
someObject.greet(); 

So the above code calls the greet method that is sitting inside the someObject object.

Next we can also make use of the call method to perform an indirect invocation, here the value of this equals to the first argument that the call method receives.

function someFunction() { 
   console.log(this);
}
const someContext = { a : 'some value' };
someFunction.call(someContext); 

For the above example code, the this keyword points to the someContext object.

Next, let us see what this equals to in a constructor function that is created using the normal function syntax :

function User() { 
   console.log(this);
}
new User(); 

If we invoke the constructor function using the new keyword, then this equals to the newly created instance.

  • Arrow Functions

Arrow functions do not have their own this, instead they look up the scope chain to find a this that they can use (or until the lookup hits the global scope) much in the same way as a variable resolves its value. So if you reference a variable in a nested scope, then the lookup will go up the scope chain until it resolves its declaration. This in essence means that arrow function does not define its own execution context. This also means that no matter how or where the arrow functions get executed, the this value inside an arrow function always equals the this value from the outer function. IOW, arrow functions resolve the this in a lexical manner.

Let us take an example :

const someObject = { 
   someMethod(elements) { 
     console.log(this); 
     elements.forEach(element => { 
        console.log(element);
        console.log(this);
     });
   }
};
someObject.someMethod([1,2,3,4,5]);

this value inside the callback of forEach equals to this of the outer function someMethod

Constructors

  • Normal Functions We now know that normal functions can be used for creating new objects. So for example, if we want to create a new user, then we can create a constructor function with the name User and that will give us the ability to create new instances of a car when used with the new keyword. So let us see the code for this :
function User(name, age, profession) {  
  this.name = name; 
  this.age = age; 
  this.profession = profession;
}
const newUser = new User('alex', 21, 'developer');
console.log(newUser instanceof User); 

So if you see above, User is a normal function. When invoked with the new keyword, new User('alex', 21, 'developer') – new instances of User gets created.

  • Arrow Functions

Arrow functions cannot be used as constructor. So if you try to invoke an arrow function using the new keyword, JavaScript will yell at you with an error. So let us see an example for this as well :

const User = () => { 
   this.name = name; 
   this.age = age;
   this.profession = profession; 
}
const newUser = new User('alex',21, 'developer'); 

So for above code, invoking new User('alex', 21, 'developer') will throw an error, specifically this error :

TypeError: User is not a constructor

– Implicit Return

  • Normal Function

See the following code :

function someFunction() { 
  return 'some value';
} 
const value = someFunction(); 
console.log(value);

In the above code, we are just invoking the normal function someFunction and at the point of invocation, we receive a value which we store in value variable that we defined above. Now if we omit the return statement in the above code, or if there is no expression after the return statement, the normal function will do an implicit return here with the value of undefined. So let me demonstrate this with the help of an example :

function someFunction() { 
  'some value';
}
function someFunction2() { 
 return;
}
console.log(someFunction());
console.log(someFunction2()); 

In above code, the first console.log will give us undefined because it misses the return keyword, it actually returns nothing. Coming to the second console.log, it does return but there is no value as such, hence this is also undefined

  • Arrow Function

In case of an arrow function, you can perform implicit return but the condition for same is that your arrow function should contain only one expression. If so is the case, you can then omit the function’s curly braces, and simply do an implicit return for the expression. So let us see an example for this :

const multiplyBy2 = num => num * 2; 
console.log(multiplyBy2(3)); 

The multiplyBy2 arrow function if you see comprises of just one expression num => num * 2. So here the expression is implicitly returned by the arrow function
without the use of return keyword.

Methods

  • Normal Function

Normal functions as we know is the usual way for defining methods on classes.

Let us see an example :

class User {
   constructor(name, age, profession) {
       this.name = name; 
       this.age = age; 
       this.profession = profession; 
   }
   logUserDetails() { 
      console.log(this.name);
      console.log(this.age);
      console.log(this.profession); 
   } 
 } 
const user = new User('alex', 21, 'developer'); 
user.logUserDetails();  

So the above code does works. But let us assume that we need to supply the logUserDetails method as a callback, say for example to a setTimeout or some other event listener. Now in such cases, we may get a wrong context while trying to access the this value.
So consider this code :

setTimeout(user.logUserDetails, 1000);

So after 1 second, the above code will call the logUserDetails method on the user object. Now for the above code this points to the window object.

Let us manually fix the context so by correctly binding this value to point to the user object. So for this , we can make use of the bind method.

setTimeout(user.logUserDetails.bind(user), 1000);

In the above code, user.logUserDetails.bind(user) binds the this value to the user instance. So this ensures that we don’t loose the context and that the this keyword is correctly bound to the instance.

There is another workaround for this as well which is using arrow functions.

  • Arrow Function

Now with the introduction of class fields, we can use the arrow functions as methods inside classes.

Contrary to the normal functions, the method that we define using arrow functions binds this lexically to the instance of the class.

So let us see an example for same :

class User {
   constructor(name, age, profession) {
       this.name = name; 
       this.age = age; 
       this.profession = profession; 
   }
   logUserDetails = () => { 
      console.log(this.name);
      console.log(this.age);
      console.log(this.profession); 
   } 
 } 
const user = new User('alex', 21, 'developer'); 
user.logUserDetails();  

Now for the above code, if we use user.logUserDetails as a callback without even performing a bind, it will give us the correct values. The value of this inside our logUserDetails will be the class instance itself.
So if you try the below code, you will get alex, 21 and developer to the console as output.

setTimeout(user.logUserDetails, 1000);

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

If you enjoy my articles, consider following me on Twitter for more interesting stuff :

Image description

‚ö°Twitter : https://twitter.com/The_Nerdy_Dev

Don’t forget to leave a like if you loved the article. Also share it with your friends and colleagues.

Leave a Reply

Your email address will not be published.