Arrow functions in JavaScript
Prerequisite: this in JavaScript
In this post, some more functions relatable with this in JavaScript have been discussed.
this and Arrow Functions: Arrow functions, introduced in ES6, provides a concise way to write functions in JavaScript. Another significant advantage it offers is the fact that it does not bind its own this. In other words, the context inside arrow functions is lexically or statically defined.
What do we mean by that?
Unlike other functions, the value of this inside arrow functions is not dependent on how they are invoked or how they are defined. It depends only on its enclosing context.
Let us try to understand with an example:
Example 1: In this example, we will create a basic object an see the use of this object in Javascript.
javascript
<script> let People = function (person, age) { this .person = person; this .age = age; this .info = function () { // logs People console.log( this ); setTimeout( function () { // here this!=People console.log( this .person + " is " + this .age + " years old" ); }, 3000); } } let person1 = new People( 'John' , 21); // logs : undefined is undefined years old after 3 seconds person1.info(); </script> |
Output:
People {person: 'John', age: 21, info: Æ’} undefined is undefined years old
The reason that we get undefined outputs instead of the proper info as output happens because the function() defined as the callback for setTimeout() has a normal function invocation and as we know, 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, non-arrow function defines its own this or context depending on their invocation. The context of the enclosing objects/function does not affect this tendency to automatically define their own context.
How do we solve this?
One obvious solution that comes to mind is, what if the function did not define its own context? What if it inherited the context from the info(), because that would mean this 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.
That is, in the above example, if the function defined as callback for setTimeout() were an arrow function it would inherit the value of this from it’s enclosing context – info()
javascript
<script> let People = function (person, age) { this .person = person; this .age = age; this .info = function () { // logs People console.log( this ); setTimeout(() => { // arrow function to make lexical "this" binding // here this=People."this" has been inherited console.log( this .person + " is " + this .age + " years old" ); }, 3000); } } let person1 = new People( 'John' , 21); // logs : John is 21 years old after 3 seconds person1.info(); </script> |
Output:
People {person: 'John', age: 21, info: Æ’} John is 21 years old
Thus, regardless of whether the arrow function was called using function invocation or method invocation, it retains the value of this from its enclosing context. In other words, an arrow function’s this value is the same as it was 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 object.
this in separated methods:
When a method from any object is separated from it, or stored in a variable, eg: let separated = People.info, it loses the reference to its calling object.
Notice the lack of opening and closing parentheses after info. This indicates that we are not calling the method immediately.
For instance :
javascript
<script> let People = function (person, age) { this .person = person; this .age = age; this .info = function () { // logs People console.log( this + ' ' ); // here this=People console.log( this .person + " is " + this .age + " years old" ); } } let person1 = new People( 'John' , 21); // logs : John is 21 years old person1.info(); // separating the method info() from its // object by storing it in a variable let separated = person1.info; // logs : undefined is undefined years old separated(); </script> |
Output:
[object Object] John is 21 years old [object Window] undefined is undefined years old
Once we separate the info() from the person1 object by storing it in separated, we lose all references to the person1 object.
We can no longer access the parent object by using this inside the separated method because the context of the separated method gets reset to the global context.
Thus, when we call separated() we see that this is now set to the global window object.
How do we solve this?
One way is to bind the value of an object with the method when storing the method in separated. This ensures that all references to this refers to this bound object even in the separated method.
This can be done using bind() like so:
javascript
<script> let People = function (person, age) { this .person = person; this .age = age; this .info = function () { // logs People console.log( this + ' ' ); // here this=People console.log( this .person + " is " + this .age + " years old" ); } } let person1 = new People( 'John' , 21); // logs : John is 21 years old person1.info(); let separated = person1.info.bind(person1); /* the bind(person1) statement ensures that "this" always refers to person1 inside the bound method- info() */ // logs : undefined is undefined years old separated(); </script> |
Output:
[object Object] John is 21 years old [object Object] John is 21 years old
Note: We could have used any object to bind() to the method info(), instead of, code.person1, and the outputs would have changed accordingly. It is not mandatory to bind a method to it’s parent object.
If you have come this far you would have noticed that we mentioned bind() a couple of times.
Let us now study what bind(), call() and apply() really are.
bind, call and apply:
bind(), call() and apply() are all used to modify the context of a function. All three of these help explicitly specify what the value of this should be inside a function. But, there are certain differences in how each of them work. Let us study these differences.
bind(): bind() allows us to explicitly define what value this will have inside a function by binding an object to that function. The bound object serves as the context(this value) for the function that it was bound to.
To use it, we need primarily two things – An object to bind to a function and a function that this object is to be bound to.
Syntax:
boundfunction = someFunction.bind(someObject, additionalParams);
The first argument used inside the bind() directive serves as this value and the arguments that follow it are optional and serve as the arguments for the bound function.
javascript
<script> let fruit = function (person, color) { this .person = person; this .color = color; this .displayInfo = function () { console.log( this .person + " is " + this .color ); } } let bindingObj = { // creating an object using object literal syntax person : "Banana" , color : "Yellow" , } // Constructor invocation to create an object fruit1 let fruit1 = new fruit( "Orange" , "orange" ); // logs :Orange is orange // Method invocation of displayInfo() fruit1.displayInfo(); // binding the separated method to a new object let newBound = fruit1.displayInfo.bind(bindingObj); // logs : Banana is Yellow newBound(); </script> |
Output:
Orange is orange Banana is Yellow
Notice that we call displayInfo() on fruit1 using method invocation: fruit1.displayInfo and therefore might expect it to have the context of fruit1. However, this does not seem to be the case as “Banana is Yellow” gets logged instead of “Orange is orange”.
This happens because we explicitly bind the value of this inside displayInfo() using the bind() command to bindingObj.
As we can see, binding an object explicitly to a function overrides its normal context rules and forcefully sets all these values to the bound object inside it.
Often, when passing functions around, it loses their context. For instance:
javascript
<script> let noBinding = { persons : "John" , passAround() { // displayInfo function is passed // as a callback to setTimeout setTimeout( this .displayInfo, 3000); }, displayInfo() { // logs the Window object console.log( this + ' ' ); // logs undefined console.log( this .persons); } } noBinding.passAround(); </script> |
Output:
[object Window] undefined
To prevent resetting of context inside the function passed as a callback, we explicitly bind the value of this to be the same as it was inside passAround(), i.e: to the binding object :
javascript
<script> let binding = { persons : "John" , passAround() { // displayInfo function is passed as a callback // to setTimeout binding the context of displayInfo // to "this" = binding in this execution context setTimeout( this .displayInfo.bind( this ), 3000); }, displayInfo() { // logs the binding object console.log( this + ' ' ); // logs John console.log( this .persons); } } binding.passAround(); </script> |
Output:
[object Object] John
call() and apply(): call() and apply() perform a task similar to bind by explicitly specifying what value this should store inside a function. However, one major difference between them and bind() is that call() and apply() immediately calls the function, as opposed to simply preparing a copy of the function with a bound value for future use.
Syntax:
call:
function.call(thisValue, arg1, arg2, ...)
apply:
function.apply(thisValue, [ arg1, arg2, ...])
The first argument in both cases is the value of this that we wish for the called function to have. Essentially, the only difference between these two methods is the fact that in apply, a second argument is an array object of arguments while in the call, all arguments are sent in a comma-separated format.
For example:
javascript
<script> // Create two different objects to test call() and apply() let banana = { person : 'Banana' }; let orange = { person : 'Orange' }; function fruits(adj) { console.log( this + ' ' ); return ( this .person + " is " + adj); } // call() and apply() will immediately invoke // the function they are being called on // logs : Banana is yummy console.log(fruits.call(banana, 'yummy' )); // logs: Orange is sour console.log(fruits.apply(orange, [ 'sour' ])); </script> |
Output:
[object Object] Banana is yummy [object Object] Orange is sour
Note: Both call() and apply() are methods that are available on the prototype of the default Function object.
this with event listeners: Inside functions are used as callbacks for event listeners, this holds the value of the element that fired the event.
javascript
<button>Click me to see console logging </button> <script> let elem = document.querySelector( 'button' ) elem.addEventListener( 'click' , function () { // logs: btn document.write( this ) }) </script> |
Output:
[object HTMLButtonElement]
If the callback function used here is an arrow function and our event listener is nested inside another method, this would refer to the context of the outer nesting method, and we can no longer access the element that the event listener is added to using this as we had in the previous code example.
Fortunately, this can be easily resolved by using the currentTarget method on the element like so:
javascript
<button>Click me to see console logging </button> <script> function outerfunc(elem) { this .clickHandler = function () { elem.addEventListener( 'click' , (e) => { // logs the<button />element document.write(e.currentTarget); // this has the value of outerfunc this .displayInfo(); }) } ; this .displayInfo = function () { document.write( ' Correctly identified!' ); } } let button = document.body.querySelector( 'button' ); let test = new outerfunc(button); test.clickHandler() </script> |
Output:
[object HTMLButtonElement] Correctly identified!
As we can see, using the currentTarget allows us to access the element that the event listener is added on while this allows us to access the context of the enclosing function – i.e: this allows us to successfully call the displayInfo() method.
Supported Browser:
- Chrome 45 and above
- Edge 12 and above
- Firefox 22 and above
- Opera 32 and above
- Safari 10 and above
Please Login to comment...