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:



filter_none

edit
close

play_arrow

link
brightness_4
code

<!DOCTYPE html>
<html>
<body>
<script>
    let People = function(person, age) {
        this.person = person;
        this.age = age;
        this.info = function() {
  
         // logs People
         document.write(this);
  
         setTimeout(function() {
            // here this!=People
           document.write(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>      
</body>
</html>

chevron_right


Output:

[object Object] 
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()

filter_none

edit
close

play_arrow

link
brightness_4
code

<!DOCTYPE html>
<html>
<body>
<script>
    let People = function(person, age) {
        this.person = person;
        this.age = age;
        this.info = function() {
  
            // logs People
            document.write(this); 
  
           setTimeout(() => { 
            // arrow function to make lexical "this" binding
            // here this=People."this" has been inherited
            document.write(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>                    
</body>
</html>  

chevron_right


Output:

[object Object] 
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 :

filter_none

edit
close

play_arrow

link
brightness_4
code

<!DOCTYPE html>
<html>
<body>
<script>
    let People = function(person, age) {
        this.person = person;
        this.age = age;
        this.info = function() {
  
            // logs People
            document.write(this + ' ');
  
            // here this=People
            document.write(this.person + " is " + this.age + 
                                      " years old" + '<br>');
        }
    }
  
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>                    
</body>
</html>

chevron_right


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:

filter_none

edit
close

play_arrow

link
brightness_4
code

<!DOCTYPE html>
<html>
<body>
<script>
    let People = function(person, age) {
        this.person = person;
        this.age = age;
        this.info = function() {
  
            // logs People
            document.write(this + ' ');
  
            // here this=People
            document.write(this.person + " is " + this.age + 
                                    " years old" + '<br>');
  
        }
    }
  
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>                    
</body>
</html>

chevron_right


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.

The general syntax for bind is :

boundfunction = someFunction.bind(someObject, additionalParams);

The first argument used inside the bind() directive serves as the this value and the arguments that follow it are optional and serve as the arguments for the bound function.

filter_none

edit
close

play_arrow

link
brightness_4
code

<!DOCTYPE html>
<html>
<body>
<script>
    let fruit = function(person, color) {
        this.person = person;
        this.color = color;
        this.displayInfo = function() {
            document.write(this.person + " is " + this.color + '<br>');
        }
    }
  
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>                    
</body>
</html>

chevron_right


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 this values to the bound object inside it.

Often, when passing functions around, it loses its context.For instance:

filter_none

edit
close

play_arrow

link
brightness_4
code

<!DOCTYPE html>
<html>
<body>
<script>
    let noBinding = {
        persons : "John",
  
        passAround() {
  
        // displayInfo function is passed
        // as a callback to setTimeout
        setTimeout(this.displayInfo, 3000);
        },
  
        displayInfo() {
  
        // logs the Window object
        document.write(this + ' ');
  
        // logs undefined
        document.write(this.persons);
  
        }
    }
  
    noBinding.passAround();
</script>                 
</body>
</html>                                                         

chevron_right


Output:

[object Window] undefined

To prevent resetting of context inside the function passed as callback, we explicitly bind the value of this to be the same as it was inside passAround(), i.e:to the binding object :

filter_none

edit
close

play_arrow

link
brightness_4
code

<!DOCTYPE html>
<html>
<body>
<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
            document.write(this + ' ');
  
            // logs John
            document.write(this.persons);
  
        }
  
    }
  
     binding.passAround();
</script>                    
</body>
</html>                                                        

chevron_right


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 this value for future use.

The 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 , the second argument is an array object of arguments while in call, all arguments are sent in a comma separated format.

For example:

filter_none

edit
close

play_arrow

link
brightness_4
code

<!DOCTYPE html>
<html>
<body>
<script>
    // Create two different objects to test call() and apply()
    let banana = { person : 'Banana' };
let orange = { person : 'Orange' };
function fruits(adj)
{
    document.write(this + ' ');
    return (this.person + " is " + adj + '<br>');
}
  
  // call() and apply() will immediately invoke
  // the function they are being called on
  
  // logs : Banana is yummy
  document.write(fruits.call(banana, 'yummy'));
  
 // logs: Orange is sour
  document.write(fruits.apply(orange, [ 'sour' ]));
</script>                    
</body>
</html>                                                       

chevron_right


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 used as callbacks for event listeners, this holds the value of the element that fired the event.

filter_none

edit
close

play_arrow

link
brightness_4
code

<!DOCTYPE html>
<html>
<body>
<button>Click me to see console logging < /button>
<script>
    let elem = document.querySelector('btn')
        elem.addEventListener('click', function() {
        // logs: btn 
       document.write(this)
     })
</script>                   
</body>
</html>

chevron_right


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, like we had in the previous code example.

Fortunately, this can be easily resolved by using the currentTarget method on the element like so:

filter_none

edit
close

play_arrow

link
brightness_4
code

<!DOCTYPE html>
<html>
<body>
<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>                    
</body>
</html>                                                   

chevron_right


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.



My Personal Notes arrow_drop_up

Check out this Author's contributed articles.

If you like GeeksforGeeks and would like to contribute, you can also write an article using contribute.geeksforgeeks.org or mail your article to contribute@geeksforgeeks.org. See your article appearing on the GeeksforGeeks main page and help other Geeks.

Please Improve this article if you find anything incorrect by clicking on the "Improve Article" button below.




Article Tags :
Practice Tags :


1


Please write to us at contribute@geeksforgeeks.org to report any issue with the above content.