DEV Community

Akansh Saxena
Akansh Saxena

Posted on

this with arrow functions in JS

Hello reader, this has always been one of the most confusing topics in JavaScript. If you have some previous knowledge of programming languages like Java, then understanding this would again be a much more difficult task because your mind will have to fight between your existing knowledge of this in other languages and learning new concepts in JavaScript even though they don't overlap at all.

Along with various other features and enhancements, ES6 brought arrow functions with it, and with the arrow functions introduction, the concept of this started having a few more tweaks. To understand its concept and behaviour with arrow functions, some previous understanding of this is definitely required. So, feel free to pause here and get some understanding of this and then jump back to the topic.

Table of contents:

  1. What are arrow functions?
  2. Understanding this behaviour with arrow functions
    • In normal declaration
    • Defined inside a global object
    • In nested function call inside an object
    • Global functions bind to an object
  3. Conclusion

What are arrow functions?

Along with various other features, ES6 brought arrow functions with it too. They are newer, easier, and much cleaner ways of writing a function. They reduce the lines of code required to declare a function and come with small differences from the normal function declaration.
Let's compare the normal and arrow function declaration syntax first.

 

//normal function
function foo1(){
    return null
}
//arrow functions
const foo2 = () => {
    return null
}
//this can further be written in much simpler way
const foo = () => return null
Enter fullscreen mode Exit fullscreen mode

As we see, arrow functions have much simpler syntax but the major difference is that arrow functions don't create their own binding but take up the binding of their parent object i.e. they inherit it from the parent scope (remember, the parent should be a non-function). To make it simpler, the arrow functions follow the same this behaviour as it is in their parent. 
For normal functions, it totally depends on how and where they are called but for arrow functions, it depends on where they have been defined. In the above example, if we console this inside both the functions then both this will have a global object in them but the reason is quite different. Let's explore the same in next section.

Understanding this behaviour with arrow functions

this behaviour in arrow function can vary in different scenarios so let's break it into smaller parts and look through them.

  • In normal declaration
//normal function
function foo1(){
    console.log(this)
}
//with arrow functions
const foo2 = () => {
    console.log(this)
}

foo1()
foo2()
Enter fullscreen mode Exit fullscreen mode

We saw in the above scenario, both consoles will print a global object(window object in case of browsers). Function foo1() is declared in a global context and is called in a global context, which is a normal function call. This means that the object calling foo1() is a global/window object(foo1() can also be called as a window.foo1()) and therefore, this in having a value of the global object and hence, for a normal function call, this always points to object calling that function. Whereas, foo2 has been declared in a global context whose this always points to a global object and hence, in arrow function this takes up the value of its parent binding, here global context and points to global/window object.

  • Defined inside a global object
//normal function
const obj1 = {
foo1 : function(){
        console.log(this)
     }
}
//with arrow functions
const obj2 = {
foo2 : () => {
        console.log(this)
     }
}

obj1.foo1()
obj2.foo2()
Enter fullscreen mode Exit fullscreen mode

To call the normal function(foo1) inside obj1, we need to first access obj1 and then call foo1 through it. Hence, foo1 is not a normal function call and it has to be called through obj1. obj1 is an object and has created its own binding(this) and calling foo1 through obj1 makes this inside obj1 to have all properties of obj1 only and not global/window object. Whereas, if we look at obj2 and foo2, then even though foo2 is declared as a key inside obj2 but still it will have a value of this inherited from its lexical scope i.e. obj2 because as defined earlier, arrow functions inherit this property from their parent and here obj2, which is parent object to foo2 has been declared in global/window context which has this binding to global/window context and therefore, passes same this (global/window) to foo2.

  • In nested function call inside an object
//normal function call inside a method
const obj1 = {
        foo1 : function(){
                function foo2(){
                        console.log(this)
                }
                foo2()
        }
}
obj1.foo1()
//arrow function call inside a method
const obj2 = {
        foo3 : function(){
                const foo4 = () =>{
                        console.log(this)
                }
                foo4()
        }
}
obj2.foo3()
Enter fullscreen mode Exit fullscreen mode

If you look closer at the code, then you can guess the output for both consoles as it has already been explained in the above two points. If you have taken a guess and it comes to be the same as what we will discuss, then congratulations to you. 

So, in foo2 the console will print the global/window object and the reason is in the definition above, any normal function call will always point its this to the global/window object. Here, foo1 is a method to obj1 but foo2 is a normal function declared inside this method and hence this pointing to global/window object. In the second scenario, foo4 is an arrow function that takes up the this from its lexical scope so here, foo4 is lexically scoped to foo3, and foo3's this is pointing to obj2 object, and hence foo4 will also point to obj2.

Here comes an interesting question. What if, foo3 is assigned with an arrow function instead of a normal function?

const obj2 = {
        foo3 : () => {
                const foo4 = () =>{
                        console.log(this)
                }
                foo4()
        }
}
obj2.foo3()
Enter fullscreen mode Exit fullscreen mode

In this case, it is certain that foo4 will always take up its this value from the parent non-function which in this case is obj2, and since, obj2 is a global object whose binding is to a global object and hence foo4's this will also point to global/window object. So, we can see a drastic change in the behaviour of functions depending if they are normal or arrow functions.

  • Global functions bind to an object
//normal function
function foo1(){
        console.log(this)
}
const obj1 = {
        name: 'Akansh'
}
const foo2 = foo1.bind(obj1)
foo2()

//arrow function
const foo3 = () => {
        console.log(this)
}
const obj2 = {
        name: 'Akansh'
}
const foo4 = foo3.bind(obj2)
foo4()
Enter fullscreen mode Exit fullscreen mode

The given examples are no different from what we have seen earlier in the above examples. The only difference is that a global function is created which is now bound to another object and hence, the functions will now be treated as they were declared inside the object.

foo2 carries foo1 bind to obj1 and hence, calling foo2() is just like calling obj1.foo1()(if foo1 was declared inside obj1) and therefore, foo2 will have its this pointing to obj1 only. Similarly, foo4 carries foo3 bind to obj2 but here foo4 this points to global/window object because we now know that arrow function inside the object will have this pointing to object's binding and here its global/window object.

Conclusion

For a normal function call, this will always point to global/window object. A normal function defined inside an object will always takes up this from the object written on adjacent left in the dot notation. Example:

const obj = {
    foo : function(){
         console.log(this)
    }
}
obj.foo()
//here, obj is the object calling foo and is on the adjacent left while writing in dot notation
Enter fullscreen mode Exit fullscreen mode

For arrow function, the simplest explanation will be, this will always inherit binding from its non-functional parent. If defined in global context, then this in arrow function points to global/window object only.

I believe that the explanation was of some help to you and provided some value. If there is something I might have missed or you would like me to address or if there are some suggestions, feel free to comment and reach out.

Top comments (0)