Demystifying how 'this' works in Javascript

Johnny Simpson - Mar 14 '22 - - Dev Community

It's something used all the time in Javascript, but often what it refers to is a mystery. In Javascript, this works quite differently to other programming languages - and it works differently depending on if you are using use strict mode or not.

If you find it hard, you aren't alone. Let's look at exactly how this works, and remove any confusion as to what it means in various contexts.

What is this in Javascript

this is a keyword in Javascript which refers to a property or set of properties within a certain context. The context we use this in alters its properties. In the global context, this refers to the global object - which in the browser is window, but is globalThis in Node.JS and other implementations of Javascript.

console.log(this); // The same as console.log(window);
Enter fullscreen mode Exit fullscreen mode

Outside of any functions or code, this is always the case. However, in different places, this means different things.

This in Functions in Javascript

In a function, this still refers to the global object. If we reference this in a function, it will by default, reference the window or globalThis object:

console.log(this); // The same as console.log(window);

function myFunction() {
    console.log(this); // The same as console.log(window);
}

myFunction();
Enter fullscreen mode Exit fullscreen mode

In strict mode, however, this inside a function is undefined.

"use strict"
console.log(this); // The same as console.log(window);

function myFunction() {
    console.log(this); // This is undefined!
}

myFunction();
Enter fullscreen mode Exit fullscreen mode

Solving with call()

This is a little confusing at first, but the reason for this is because we need to add a this object onto myFunction - Javascript in strict mode won't default it to the global object. To do that, we have to use call(). In the example below, I've turned myObject into our this variable:

"use strict"
console.log(this); // The same as console.log(window);

let myObject = {
    firstName: "John",
    lastName: "Doe",
    age: 76
}
function myFunction() {
    console.log(this.firstName);
}

myFunction.call(myObject); // this.firstName is defined as "John", so it will console log John
myFunction(); // this.firstName will be undefined, and this will throw an error.
Enter fullscreen mode Exit fullscreen mode

call() runs myFunction and attaches myObject to the this keyword. If we don't use call, and simply run myFunction(), then the function will return an error, as this.firstName will be undefined. You are also able to call a function with an empty this, which you can then append data to inside your function.

This gives us a fresh space to define variables on our this object, rather than being polluted with data from the global this object:

"use strict"
console.log(this); // The same as console.log(window);

function myFunction() {
    this.firstName = 'John';
    console.log(this.firstName); // This will be "John"
}

myFunction.call({});
Enter fullscreen mode Exit fullscreen mode

Different behaviour in strict mode

As you can see, the behaviour is quite different depending on if we're using strict mode or not - so it's important you do some tests before changing your code between the two modes.

Call and Apply

You may sometimes see call() being used interchangeably with a function called apply(). Both of these functions are very similar, in that they both invoke a function with a specified this context. The only difference is apply() takes an array if a function has arguments, while call() takes each argument one by one.

For example:

"use strict"
let otherNumbers = {
    a: 10,
    b: 4
}
function multiplyNumbers(x, y, z) {
    return this.a * this.b * x * y * z
}

// Both will return the same result, the only difference
// being that apply() uses an array for arguments.
multiplyNumbers.call(otherNumbers, 1, 2, 3);
multiplyNumbers.apply(otherNumbers, [ 1, 2, 3 ]);
Enter fullscreen mode Exit fullscreen mode

Simplifying this process using bind()

Another way to achieve similar behaviour to call() is to use bind(). Similar to call(), bind(), changes the this value for a function, only it does so permanently. That means you don't have to constantly use bind() - you only use it once.

Here is an example, where we bind our object permanently to our function, thus updating this permanently - we just have to define it as a new function. In the below example, we define a new function called boundFunction, which is our myFunction with myObject bound to it permanently.

As such, when we call console log, it will show "John". This is different than call, which needs to be used each time we use a function.

"use strict"
console.log(this); // The same as console.log(window);

let myObject = {
    firstName: "John",
    lastName: "Doe",
    age: 76
}
function myFunction() {
    console.log(this.firstName);
}

let boundFunction = myFunction.bind(myObject); // this will bind this to myObject permanently.
boundFunction(); // since we used bind, this will now be set to myObject, every time we call boundFunction() - so it will return John.
Enter fullscreen mode Exit fullscreen mode

Arrow Notation Functions and this

One of the key features to the arrow notation functions in Javascript is they hold no this context. That means that they inherit this from their parent. For example, let's say we are in strict mode and define both an arrow function and a "normal" style function. For the arrow function, this will be inherited, but for the other function, this will remain undefined!

"use strict"
console.log(this); // The same as console.log(window);

function myFunction() {
    console.log(this.name); // This will be "John"
    let myArrowFunction = () => {
        console.log(this.name); // This will be "John"
    }

    let myNormalFunction = function() {
        console.log(this.name); // This will throw an error, since this is undefined!
    }

    myArrowFunction();
    myNormalFunction();
}

myFunction.call({
    name: "John"
});
Enter fullscreen mode Exit fullscreen mode

Constructor Functions and this

Another interesting thing about this is that when used in a constructor function (that being a function using the new keyword), the return of the constructor function essentially overwrites this. So for example, if we run the following, although we set this.name to John, the value returned for name is Jack:

let functionA = function() {
    this.name = "John";
}

let functionB = function() {
    this.name = "John";
    return {
        name: "Jack"
    }
}

let runFunctionA = new functionA();
console.log(runFunctionA.name); // Returns "John";
let runFunctionB = new functionB();
console.log(runFunctionB.name); // Returns "Jack";
Enter fullscreen mode Exit fullscreen mode

This in an Object Context

In an object context, using this refers to the object. For example, suppose we run a function within an object called obj, which refers to this.aProperty - this, in this case, refers to obj:

let obj = {
    aProperty: 15,
    runFunction: function() {
        console.log(this.aProperty); // Refers to 15
    }
}

obj.runFunction(); // Will console log 15, since this refers to obj
Enter fullscreen mode Exit fullscreen mode

This is also true if you use the get()/set() notation:

"use strict"
let obj = {
    aProperty: 15,
    runFunction: function() {
        console.log(this.aProperty); // Refers to 15
    },
    set updateProp(division) {
        this.aProperty = this.aProperty / division; // this.aProperty refers to 15
        console.log(this.aProperty); 
    }
}

obj.updateProp = 15; // Will divide aProperty by 15, and console log the result, i.e. 1
Enter fullscreen mode Exit fullscreen mode

Using this with Event Listeners

Another quirk of Javascript's this is that when using an event listener, this refers to the HTML element the event was added to. In the below example, we add a click event to an HTML tag with ID "hello-world":

document.getElementById('hello-world').addEventListener('click', function(e) {
    console.log(this);
});
Enter fullscreen mode Exit fullscreen mode

If we then click on our #hello-world HTML element, we will see this in our console log:

<div id="hello-world"></div>
Enter fullscreen mode Exit fullscreen mode

Using this with Classes

It's worth noting in this section, that classes in Javascript are simply functions under the hood. That means a lot of the functionality we've seen with functions stands true for classes.

By default, a class will have this set to the class instance itself. In the below example, we can see this in action - both runClass.name and runClass.whatsMyName return John.

class myClass { 
    whatsMyName() {
        return this.name;
    }
    get name() {
        return "John";
    }
}

const runClass = new myClass();
console.log(runClass.name);        // Returns "John"
console.log(runClass.whatsMyName); // Returns "John"
Enter fullscreen mode Exit fullscreen mode

The only exception to this, is that static items are not added to this. So if we define a function with the keyword static in front of it, it will not be on this:

class myClass { 
    getMyAge() {
        return this.whatsMyAge();
    }
    static whatsMyAge() {
        return this.age; 
    }
    get name() {
        return "John";
    }
    get age() {
        return 143
    }
}

const runClass = new myClass();
console.log(runClass.whatsMyAge()); // Throws an error, since runClass.whatsMyAge() is undefined
console.log(runClass.getMyAge()); // Throws an error, since this.whatsMyAge() is undefined
Enter fullscreen mode Exit fullscreen mode

It's worth noting that classes, by default, are always in strict mode - so this will behave in the same way it does for strict functions by default in classes.

Conclusion

In Javascript, this can mean various things. In this article, we've covered what it means in different contexts - functions, classes, and objects. We have covered how to use bind(), call() and apply() to add a different this context to your functions.

We've also covered how to use this in strict mode, versus non-strict mode. After this, I hope this is slightly demystified.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .