Demystifying ‘this’ in Javascript ES6

For a long time I was confused by the ‘this’ keyword in JS. It’s always been straightforward enough to use it within the class context, but outside that, the behaviour of ‘this’ varies.

For one, you have to consider different types of functions. The behaviour of ‘this’ changes depending if you are using an arrow function or a standard function invocation. It also changes when you run the code in a browser vs Node.

Because of all this, I was always afraid to use ‘this’ anywhere except the class context.

Functions vs Methods

First let’s clarify the difference between methods and functions. A method lives inside an object, and can be invoked by calling it from within the object.

A function exists in and of itself, and can be invoked simply by calling itself.

A function may exist within a method.

Standard Functions vs Fat Arrow – Outside Object / Class Context

Let’s first consider functions which exist at the top-level, outside a class.

The main difference between ‘this’ when it comes to standard functions vs fat arrow functions is:

Fat Arrow Functions:

  • Nodejs
    • ‘this’ refers to the ‘exports’ object
  • Browser
    • ‘this’ refers to the global ‘window’ object

Standard Functions:

  • Nodejs
    • ‘this’ refers to the ‘globals’ object
  • Browser
    • ‘this’ refers to the global ‘window’ object

‘this’ in NodeJs

Let’s consider the following code. We’ll run this in NodeJs:

function insider() {
    this.insidervar = 'SET';
}

insider();
console.log(global.insidervar);

Running the code, we can see that the output is ‘SET’. The standard function has been assigned to the insidervar member of the global object.

Now try something similar with this arrow function:

const insider = () => {
    this.insidervar = 'FAT ARROW SET';
};

insider();
console.log(global.insidervar);

In this case, the output is ‘undefined’. The fat arrow function this does not reference the global object. Instead, it assigns the value to the exports object.

console.log(exports.insidervar);

Running this gives us our desired output: ‘FAT ARROW SET’.

Why does this happen?

Arrow functions make use of lexical binding, which means they bind this to the originating context. Standard functions, on the other hand, bind to the global context.

In the case of NodeJs, the originating context is generally the commonjs module. Modules are referenced via the exports object. Therefore, fat arrow functions will bind this to the exports object.

Standard functions, however, bind to the global context. Hence, any mention in a standard function of this will reference the global object.

What About Nesting?

What happens if we nest a standard function within another standard function? What about a fat arrow function within a standard function?

Let’s see what happens when we nest a fat arrow function inside a standard function:

function insider() {
    const insideout = () => {
        this.insidervar = 'NESTED FAT SET';
    };
    insideout();
}

insider();
console.log(global.insidervar);

The output is ‘NESTED FAT SET’. In this case, the fat arrow function assigns the value to the global object. Why? Recall that the fat arrow function applies lexical binding and references this from the originating context. In this case the originating context is the insider() standard function. Because the insider() function references the global object, the nested fat arrow function will also reference the global object.

What happens when we try nesting one more fat arrow function?

function insider() {
    const insideout = () => {
        const insideoutb = () => {
            this.insidervar = 'NESTED FAT SET';
        };
        insideoutb();
    };
    insideout();
}

insider();
console.log(global.insidervar);

Again, the value has been assigned to the global object.

Let’s try things the other way round. I’m going to nest a standard function within a fat arrow function.

const insider = () => {
    function setInside() {
        this.insidervar = 'SET';
    }

    setInside();
};

insider();
console.log(global.insidervar);

You can see here that the standard function has assigned the value to the global object, despite being nested in the fat arrow function.

Finally, let’s see what happens if we nest a fat arrow function within the standard function above:

const insider = () => {
    function setInside() {
        const testInside = () =>{
            this.insidervar = 'SET';
        };
        testInside();
    }

    setInside();
};

insider();
console.log(global.insidervar);

The fat arrow function takes the context of the parent function, and the value is assigned to the global object.

‘this’ in a Browser Window

In both cases – standard and arrow functions – this will reference the global Window object of the browser. In the case of the fat arrow function, the reason is because the context just happens to be the global object. Contrast this with standard functions where this is always a reference to the global object.

<html>
    <script>
        const testarrow = () => {
            this.fatarrow = 'SET ARROW';
        };

        function testfunc(){
            this.funcoutput = 'SET FUNC';
        }

        testarrow();
        testfunc();

        console.log(window.fatarrow);
        console.log(window.funcoutput);
    </script>
</html>

The output for both is successful. The values have been assigned to the global window object. We could just as easily reference the global window object from outside our functions with simply the this keyword.

Replacing window with this will yield the same output:

console.log(this.fatarrow);
console.log(this.funcoutput);

Nesting our functions will yield similar results. This is because all of our functions, regardless of type, are referencing the global window object.

<html>
    <script>
        const testarrow = () => {
            const xxx = () => {
                this.fatarrow = 'SET FAT';
            }
            xxx();
        };

        function testfunc(){
            const xxx = () => {
                this.funcoutput = 'SET FUNC';
            }
            xxx();
        }

        testarrow();
        testfunc();

        console.log(this.fatarrow);
        console.log(this.funcoutput);
    </script>
</html>

this in a Class

Notice that in the following code, testerfunc() nested within the testme() method yields undefined when referencing this. If we assign a function to a method and try to reference this from within that function, we will receive an error because the value is undefined:

class Tester {
    constructor() {
        this.test = 'ORIGINAL';
    }

    testme() {
        function testerfunc() {
            this.test = 'SET';
        }

        testerfunc();
    }
}

const testers = new Tester();

testers.testme();

console.log(testers);

To fix this issue, we could try returning the output of testerfunc() and assigning it to the test member, like this:

class Tester {
    constructor() {
        this.test = 'ORIGINAL';
    }

    testme() {
        function testerfunc() {
            return 'SET';
        }

        this.test = testerfunc();
    }
}

const testers = new Tester();

testers.testme();

console.log(testers);

The above code will give us the desired output. this.test will be assigned the value ‘SET’.

A better way of fixing the issue is not to use a standard function inside a method at all, but rather use a fat arrow function. Because the fat arrow function uses lexical scope, the this keyword will reference the class, even when nested inside other fat arrow functions.

The following code will print an instance of the Tester class with the test member as ‘SET’.

class Tester {
    constructor() {
        this.test = 'ORIGINAL';
    }

    testme() {
        const testerfunc = () => {
            const testerfuncb = () => {
                this.test = 'SET';
            };
            testerfuncb();
        };
        testerfunc();
    }
}

const testers = new Tester();

testers.testme();

console.log(testers);

Let’s try nesting a fat arrow function inside a standard function.

class Tester {
    constructor() {
        this.test = 'ORIGINAL';
    }

    testme() {
        function testerfunc() {
            const testerfuncb = () => {
                this.test = 'SET';
            };
            testerfuncb();
        }
        testerfunc();
    }
}

const testers = new Tester();

testers.testme();

console.log(testers);

We get an error. this is undefined. Again, because the standard function yields an undefined this, the fat arrow function, which inherits that scope, will also yield an undefined this.

The same is true of all the above cases in the browser.

‘use strict’

The scenarios above change when the ‘use strict’ string is added to the top of your code.

For example, in Node, when we call this from an arrow function, the value is still assigned to the exports object. Likewise, in the browser, an arrow function will still assign this to the window object.

This works for arrow functions because arrow functions inherit their context from the calling object.

However, standard functions behave differently in strict mode when using this.

In both cases for standard functions – Node and browser – this returns undefined. This is because when a function isn’t called by a specified object, it defaults to the global object. This default is obviated in strict mode, and thus becomes undefined.

Leave a Reply