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