How `this` keyword is determined in JavaScript?


Photo of my chubby face smiling to you

Piotr Staniów • Posted on 06 Apr 20216 mins

#JavaScript

This article is a part of JavaScript Essentials series.

The curse of this keyword. It's probably amongst the top 5 most popular jokes about JavaScript. It is also the top #1 reason for unexpected TypeError: Cannot read property 'xyz' of undefined error popping up in your console out of the blue.

"

Did you know? The error Cannot read property 'xyz' of undefined doesn't refer to the value assigned to xyz being undefined. ⚠️ Instead, it means that whatever preceded xyz is undefined, for instance objectX would be undefined if the line leading to error was objectX.xyz.

It's also subject of one of my favourite questions during software engineering interviews, helping to gauge how profound someone's knowledge about JS is. It's not a must-have skill but helps while getting to know each other, telling a lot about the candidate and how they express their knowledge, so it's a perfect way to shine on (some) interviews!

Without further ado, let's answer this question:

How this keyword is determined in JavaScript?

  • For functions declared using function keyword (or methods), what this points to depends on how the function is called,
  • For arrow functions, this is taken from lexical scope like if it was a variable in closure

"Classic" function case - function keyword

Most frequently it falls under the first case, and then it's determined based on 4 rules relating to how a function containing this keyword was called.

The most important takeaway from the article is that it depends on the way in which the function gets called, and it actually doesn't matter where or how it's declared.

For functions declared using function keyword, the rules are as follows:

  1. With new operator preceding the function, this will point to a newly created object.

  2. When the function gets called using ABC methods (apply, bind*, call) — this is bound to the first argument of these methods.

  3. When the function invocation is part of a chain (e.g. object.method() or obj1.obj2.fn()) then this will point to the value of the chain.

  4. When the function is called without any syntactic adornments – like callMe() – then this will point either to the global object (like window) or undefined in the strict mode.

These 4 rules are, in my experience, crucial to understanding what this points to, at least in functions declared with function keyword. However, there are also other cases like arrow functions, that are explained after the Examples section.

Examples

Below are a few examples to help you wrap your head around these four rules, you can skip them, I'd recommend reading them however and trying to think what happens before you jump to my comments. They're not in any particular order.

Example 1
function Car() {
    this.wheels = 4;
    this.engine = {};
}

const car = new Car();
car; // { wheels: 4, engine: {} }

In the example above, it is the line const car = new Car(); that solely dictates what this points to, and the key is that we're using the new operator when calling function Car.

The way new operator works, it creates a brand new object "out of nowhere" (a bit like the {} literal), and calls the Car function with this pointing to that newly created object. (It also does a few other things to be super-precise).

There's nothing preventing us from actually trying to call Car without the new operator - const car = Car(); - can you think which rule would then apply and what this would point to in that case?

Example 2
function eventHandler(auxA, auxB) {
  'use strict';
  console.log(this.name, auxA, auxB);
}

const event = {
  name: 'click',
};

eventHandler.apply(event, [10, 20]);
eventHandler.bind(event, 10, 20)();
eventHandler.call(event, 10, 20);

This next example demonstrates how .apply, .bind and .call allow you to take control over what this points to. Commonly called as ABC methods, they're defined for every function in JavaScript. Apply and call both let you call the function, and whatever is passed to them as first argument will be bound to this keyword (event becomes this, in this scenario). Furthermore, they allow passing arguments to the function, either as an array (.apply) or as "standalone" arguments (.call).

The .bind is slightly different, as it doesn't actually call the function but instead returns a brand new function. The new (bound) function has both this keyword and its arguments already assigned. You can assign it then to a variable or pass somewhere as a callback, and call it elsewhere. Hence the double parentheses in invocation: eventHandler.bind(event, 10, 20)(); – calling .bind() only returns a bound function, it's the second parentheses that invokes it in this example.

Can you think which rule would be applied if you called eventHandler() straightaway? What would it result in?

Example 3
function print() {
    console.log(this.message);
}

const entry = {
  message: 'Hello',
  nested: {
    message: 'Hi!',
    print,
  },
  print,
};

entry.print();
print();

The last two rules are covered with this example - we're declaring a standalone function in the global scope. Yet, this does not affect value of this. The function is assigned to a property in entry object – however, this also does not affect it.

The last two lines are where this value is actually determined. Neither of them is called using new operator. ABC methods weren't used. So it must be the last two rules. In the case of entry.print(), to simply put this will point to whatever precedes the dot before function name – entry. Similarly, in case of entry.nested.print() that'd be entry.nested.

In the last statement though, print() will either print out undefined or will actually throw an error. Depending on whether the function/module scope has the strict mode turned on/off, this will point either to undefined or to the global object (in case of web browsers – window).

In consequence, this either reads global variable window.message or throws an error, as we attempt to read property message of undefined.

Example 4
const api = {
  getData: function() {
    fetch('https://www.google.com')
      .then(async function (res) {
        console.log('FETCHED');
        this.response = await res.text();
        console.log('ASSIGNED');
      });
  }
};
api.getData();

Let's say you run the last example (probably while being on Google website, or CORS kicks in), and both FETCHED and ASSIGNED messages were printed out. Can you tell where the response was assigned and which rule dictates that?

(Answer will follow later).

What this points to in arrow functions?

In case of arrow functions this is evaluated like if it was an implicit variable in a closure - it's taken from function lexical scope.

Understanding what this points to in arrow functions is certainly a bit more intuitive when dealing with nested functions or classes – take a look at the example above. The response was assigned to global scope, meaning the data is in window.response, and the reason for that is there's an anonymous function async function (res) { ... } being passed without any context to .then(<here>).

You can think of it as if it was executed by the browser somewhere else, if you were implementing Promise.prototype.then, this is likely how it could look like:

Promise.prototype.then = function(onSuccess, onError) {
  // ...
  onSuccess(result);
  // ...
}

As you can see, this is pretty much the fourth rule applied, so this inside the function passed as onSuccess callback is pointing to global scope.

This is not exactly obvious but it's actually one of most frequently encountered issues with this - when you're passing a function to another function to invoke it, it's likely to be called without any context at all. (There are exceptions however, like in the case of addEventListener…).

Arrow function can be helpful in this particular scenario, let's see a mixed example:

const api = {
  getData: function() {
    const mySweetTemporaryVariable = 17;
    fetch('https://www.google.com')
      .then(async (res) => {
        console.log('FETCHED');
        this.response = await res.text();
        console.log('ASSIGNED');
      });
  }
};
api.getData();

In this example, this keyword is no longer located in a function defined with function keyword, so these four rules no longer (directly) apply. Instead, as it's in arrow function, it's taken from the lexical scope, as any other variable in closure - like the mySweetTemporaryVariable. In turn, the function holding the sweet temporary variable is declared with function, so inside of it this is determined based on these rules.

Finally, api.getData() call falls into the third rule, this points to api inside of getData, and because async (res) => {...} is declared in getData it will borrow this from there.

What's a game-changer here is that lexical scope is preserved, so even if you pass the function somewhere else, the value of this is preserved:

const obj = {
  id: '1',
  fun1: function() {
    const that = this; // Just to show the lexical scope
    obj.fun2 = () => console.log(this); // this is always same as that
  },
};

const obj2 = {
  id: '2',
  funA: function() {
    obj.fun1(); // This is where `this` is bound in `fun1`
    this.funB = obj.fun2;
    this.funB(); // `this` is intact and same as `that` was previously
  },
};

obj2.funA();

Chances though are, you may still encounter a counterintuitive example like this:

const obj = {
  fn: () => console.log(this),
};
obj.fn();
this; // window

In this case, fn is located in global scope - there's no function surrounding it, and obj is an object, so it doesn't count. As a result, this points to whatever this referred to outside of fn, that is - window.

What this points to in classes?

In case of classes this will behave largely like in the case of functions declared using function keyword, following same set of rules. This is because classes in JavaScript are mostly syntactic sugar for functions.

class Car {
    constructor(make, model) {
        this.make = make;
        this.model = model;
    }

    print() {
        console.log(this.make, this.model);
    }
}

const car = new Car('Skoda', 'Enyaq'); // A
car.print(); // B
const printer = car.print;
printer.call(car); // C
printer(); // D

The example above gives good overview of these rules being applied - when new is used, this inside constructor points to a newly instantiated object. (See line A)

Next, in line commented as "B", car.print() "properly" accesses instance data because third rule applies - we're calling function as a method of some object, using dot operator, so whatever precedes it - car - gets assigned to this.

If we however need to pass car.print somewhere as an argument though, or if it is assigned to a variable, the context of "class Car" is not bound in any way to the function. Hence .apply, .bind or .call can still be used to tell what this points to (example C).

Finally, the last line - // D will simply error, since this would point to undefined (classes use strict mode by default).

How to assign this in JavaScript classes?

There are two main ways of circumventing issues with this in classes. One is to bind this where it's value is well-known and intuitive - in a constructor. Another idea is to not use methods syntax (they behave as if they were declared using function keyword), but instead use arrow functions assigned to class fields.

For brevity and its comprehensibility, my preferred is actually the latter option, so I'll only show the that one in action 😉

class Car {
    constructor(make, model) {
        this.make = make;
        this.model = model;
    }

    print = () => {
        console.log(this.make, this.model);
    };
}

Notice that in the example arrow function points to the lexical scope, which for classes fortunately is exactly the instance that we (most likely) want to refer to. It's also worth mentioning, it's technically not a method anymore, but an arrow function assigned to a class field (not a static one though).

I can't however immediately think of any practical implications of this difference, can you enumerate any?

Wrap up

Learning all rules governing what this refers to can be intimidating at first, but when you actually read it once, you know what to look for. In reality, it's important to keep in mind two main things:

  • For functions declared using function keyword (or methods), what this points to depends on how the function is called,
  • For arrow functions, this is taken from lexical scope like if it was a variable in closure

Even if not mandatory for a successful javascript developer, knowing all these details in and out is certainly helpful when debugging issues, or it can simply help you shine as a JavaScript expert during coding interviews. Having said that, you're now amongst the experts, go spread the word! 🚀

Stay connected

Stay up to date with recent articles — subscribe to the newsletter.
Unsubscribe at any time with a link in the email.

© Piotr Staniów's Blog 2023

Opinions are my own and do not represent the views of my current or past employers.