fbpx Skip to content

Aquent | DEV6

Demystifying ‘this’

Written by: James McGeachie

The JavaScript language has many quirks that can confuse both junior and experienced developers alike. One of these is the unintuitive behavior of the ‘this’ keyword. In this post, we’ll explain how the differences in ‘this’ value arise from how the enclosing function is invoked, and how you can leverage arrow functions and the call, apply and bind methods to avoid the pitfalls.

English

Before we jump into technical details, let’s think about the use of the word in English. Take the following question:

            “What is the first letter in this?”

What does ‘this’ refer to? It could be the word, the sentence or the paragraph. We can tell that it refers to some surrounding context, but without more information or a language rule to apply, we have to make an assumption about what that context is. We can solve this by being more specific, e.g.

            “What is the first letter in this sentence?”

From the above, you can take away 2 points:

‘this’ can be used to refer to an enclosing context.

Without further clarification, it is inherently vague.

Context

The function of ‘this’ in programming is similar to its use case in everyday language. We use it to reference execution context. Let’s jump into some examples to demonstrate.

class Phone {
 
  
  makeCall(phoneNumber) {
 
    const calls = [];
 
    calls.push(phoneNumber);
 
  };
 
};

Here I have a Phone class, with a simple makeCall method. I declare an array ‘calls’ and push a phoneNumber into it. Problem – I’ve declared calls inside makeCall, which means it is only accessible within the local scope, not useful for other instance methods.  Let’s pull it out to the instance level:

class Phone {
  
 
  constructor() {
 
    this.calls = [];
 
  };
 
  
 
  makeCall(phoneNumber) {
 
    calls.push(phoneNumber);
 
  };
 
};
 
 
 
const phone = new Phone();
 
phone.makeCall('647 555 5555');

Ok, the calls array is now an instance variable. Let’s run it.

 calls.push(phoneNumber);
 ^
 
ReferenceError: calls is not defined

Ouch. the engine doesn’t look outside of the method’s scope to find the variable. And the ’calls’ variable is no longer declared inside this method. To solve, we can use ‘this’.

class Phone {
 
  
 
  constructor() {
 
    this.calls = [];
 
  };
 
  
 
   makeCall(phoneNumber) {
 
    this.calls.push(phoneNumber);
 
  };
 
};
 
  
 
const phone = new Phone();
 
phone.makeCall('647 555 5555');
 
console.log(phone.calls);
 
// [ '647 555 5555' ]

Success! This works because in the constructor I attached ‘calls’ to the new instance and then later referenced it when pushing the phone number. I could do that because ‘this’ is designed to refer to the execution context of the instruction. In this case, the engine has decided the execution context is the Phone instance.  I solved this problem by being more specific about where the ‘calls’ are located.

The instance reference is a common use case for ‘this’ in object-oriented programming. You will see the same behavior in other languages, such as Java. Unfortunately, that’s not the end of our story.

JS Quirks

Earlier I said in English it can sometimes be up to the reader to determine the intent of ‘this’. The same applies to programming runtimes.  The engine has to make a decision about what execution context you’re trying to refer to. Ideally, we want the behavior to be predictable. This is not the case in JS. Let’s return to my previous example, with one change.

class Phone {
 
  
 
  constructor() {
 
    this.calls = [];
 
  };
 
  
 
  makeCall(phoneNumber) {
 
    this.calls.push(phoneNumber);
 
  
 
    function logCalls() {
 
      console.log(this.calls)
 
    };
 
  
 
    logCalls();
 
  };
 
};
 
  
 
const phone = new Phone();
 
phone.makeCall('647 555 5555');

I’m now logging this.calls inside an inner function within makeCall. Intuitively, I believe that ‘this’ is still referring to the Phone instance.

 console.log(this.calls)
                  ^
 
TypeError: Cannot read property 'calls' of undefined

So why do I get a TypeError? I’m pretty sure that the instance is defined, or our earlier code would’ve thrown an exception. My mistake is that I’m assuming the value of the ‘this’ keyword is still referring to the phone instance. That would be predictable behavior. Unfortunately, in JavaScript the value of ‘this’ is dependent on how the containing function is invoked.  JS has its own unique rules about what it considers the execution context to be in different invocation cases.

Scary? Fear not, that’s why we’re here. We can avoid being caught out by these rules by learning and understanding them.  The differences come from whether you’re invoking a regular function, a method or a constructor function. Let’s break down the behavior for each:


Function vs Method calls

The execution context is different for functions than for methods. 

Below I have an object literal ‘foo’ that has a method ‘bar’ (Note – a method is a function that’s defined on an object).

const foo = {
 
  baz: 'baz!',
 
  bar: function() {
 
    console.log(this.baz);
 
  }
 
};
 
  
 
foo.bar();
 
// baz!

When I call foo.bar, it prints the value of the ‘baz’ property, which makes sense. Now, let’s change baz to be a method with an inner function call.

const foo = {
 
  baz: function() {
 
  
 
    function _baz() {
 
      console.log(this);
 
    };
 
  
 
    _baz();
 
  },
 
  bar: function() {
 
    console.log(this);
 
  }
 
 };
 
  
 
 foo.bar();
 
 // { baz: f, bar: f}
 
 foo.baz();
 
 // Window

When it’s run in a browser JS console, you’ll see these outputs. In both cases I’m logging the value of ‘this’. In the case of invoking ‘bar’, ‘this’ is the ‘foo’ object. But for ‘baz’, it’s the window object (i.e., the ‘global context’). This is a common source of bugs! Because ‘Window’ is a valid object, no exception is thrown when you read the entirely wrong value from it.

 Now we can establish the 2 most important rules when dealing with ‘this’:

  • The ‘this’ value inside an object method, refers to the containing object.
  • The ‘this’ value inside a non-method function, defined with the function keyword, refers to the global context.

You can mitigate this through the use of strict mode. Doing so causes the ‘this’ value inside the function to be ‘undefined’, ensuring an error is thrown if you try to read a property from it, which hopefully means you’ll catch it earlier.

Two More Examples

Let’s look at 2 more examples to really hammer home how ‘this’ behaves.

Calling an externally defined function as a method

'use strict';
 
function bar() {
 
  console.log(this);
 
};
 
  
 
const foo = {};
 
  
 
bar();
 
// undefined
 
foo.bar = bar;
 
foo.bar();
 
// { bar: f}

First, I declare ‘use strict’. Then I define bar and call it and get undefined, which is expected because of strict mode. I then attach bar to foo, so it becomes foo’s method. Now, it logs foo. Great.

Passing in a callback

What if I try to reproduce the ‘inner function’ problem from before by passing a callback function defined outside of foo?

'use strict'
 
function bar() {
 
  console.log(this);
 
};
 
  
 
const foo = {
 
  bar: function(callback) {
 
    callback();
 
  }
 
};
 
  
 
bar();
 
// undefined
 
foo.bar(bar);
 
// undefined

The answer is I print undefined. The ‘this’ value has nothing to do with the fact I passed the function around, but everything to do with whether or not I invoke the function as a method. If you remember that, you’ll avoid a lot of problems.


Constructor Functions

The execution context for a constructor function is the new object that the constructor returns.

The class keyword I used earlier is new to JavaScript and is syntactic sugar on the old system of prototypes. The ‘old way’ of having blueprints for objects was to use constructor functions. These are functions called with the ‘new’ keyword in front of them.

Let’s take a look at an example that’s similar to the one from our earlier Phone class. Note: Strict mode is off.

function Foo() {
 
  console.log(this);
 
  
 
  this.bar = function() {
 
    console.log(this);
 
  
 
    function baz() {
 
      console.log(this);
 
    };
 
  
 
    baz();
 
  };
 
};
 
const foo = new Foo();
 
// Foo {}
 
foo.bar();
 
// Foo {bar: f}
 
// Window

The first log is the newly created Foo instance, because assigning ‘this’ to the instance is the normal behavior for a constructor function. The method and inner function invocations have the same behavior as we’ve shown before. Method ‘this’ points to the instance, function ‘this’ points to global.

However, there’s one big difference from my Phone example. I said strict mode is off here, hence I get ‘Window’ in the inner function. Well it was also off when I called the inner function in the Phone example, except in that case, this happened:

      console.log(this.calls)
                       ^

TypeError: Cannot read property 'calls' of undefined

If the execution context is ‘Window’ now, why was it undefined before? The answer is that earlier I benefited from a useful side-effect of using ES6 classes – you get ‘strict mode’ behavior by default. As I’m not using the class keyword this time, I don’t get that benefit. For this reason, I encourage using the class keyword by default.

Now, let’s look at ways we can limit unpredictable ‘this’ behavior without having to memorize these rules or rely on strict mode.

Controlling ‘this’

We have learned the main quirks of how ‘this’ value is assigned in JavaScript. However, what if we want to avoid being at the whim of the engine’s ‘this’ value assignment? The answer is control. There are 2 approaches to control the value of ‘this’: By assigning it ourselves, or retaining the previous value.


Assigning Value

JavaScript contains 3 ways to set a specific ‘this’ value for a single function invocation. These are CallApply and Bind. They are used as follows:

  • Call and Apply allow you to choose the ‘this’ value at invocation time.
  • Bind allows you to define ‘this’ prior to invocation time.
const foo = {
 
  bar: function() {
 
  
 
    const _bar = function() {
 
      console.log(this);
 
    };
 
  
 
    _bar.call(foo);
 
  }
 
};
 
  
 
foo.bar();
 
// {bar: f}

Here I invoke _bar using its Call method and pass foo as the ‘this’ argument. So when I log out ‘this’, it’s foo, not Window or undefined.

Apply works the same as Call, the difference between them is how you pass arguments. Call takes a comma separated list, Apply takes an array. The reasons for this are outside of the scope of the post, but you may find the difference useful.

const foo = {
 
  bar: function() {
 
  
 
    const _bar = function(a, b) {
 
      console.log(this, a, b);
 
    };
 
  
 
    _bar.call(foo, 1, 2);
 
    _bar.apply(foo, [1, 2]);
 
  }
 
};
 
  
 
foo.bar();
 
// {bar: f} 1 2
 
// {bar: f} 1 2

Bind is different. It sets the value of ‘this’ that is always used whenever you invoke the function.

const foo = {
 
  bar: function() {
 
  
 
    const _bar = function() {
 
      console.log(this);
 
    };
 
    
 
    const fooBar = _bar.bind(foo);
 
    fooBar();
 
    fooBar();
 
  }
 
};
 
  
 
foo.bar();
 
// {bar: f}
 
// {bar: f}

Note:  Bind returns a new function. It does not mutate the original. So here I have to call the new bar to get the value we want. With this behaviour, we can create multiple functions from the base foo function, which all have a different ‘this’ value.

const foo = function() {
 
  console.log(this);
 
};
 
  
 
const bar = { name: 'bar' };
 
const baz = { name: 'baz' };
 
  
 
const fooBar = foo.bind(bar);
 
const fooBaz = foo.bind(baz);
 
  
 
fooBar();
 
// { name: 'bar' }
 
fooBaz();
 
// { name: 'baz' }

Retaining Value

Wouldn’t it be great if we could just make inner functions reference the right ‘this’?

Well, the ES6 spec introduced “Arrow functions” to JavaScript which essentially do that. When you use an arrow function, the ‘this’ value remains the same as it was before; it doesn’t take on the value of the new execution context as it would with the function keyword.

const foo = {
 
  bar: function() {
 
    const _bar = () => {
 
      console.log(this);
 
    }
 
    _bar();
 
  }
 
}
 
  
 
foo.bar();
 
// {bar: f}

Here I print foo as that was the ‘this’ value of the execution context prior to when _bar was invoked. I’ve now solved the “inner function problem” without a single additional line of code. All I had to do was to modify the original function definition to use the arrow style.

Arrow functions have become the most common tool in modern JavaScript development for making dealing with ‘this’ more manageable. I encourage you to make use of them for this purpose.

Final Thoughts

Some people are making efforts to limit your need to deal with ‘this’, so you avoid the headaches entirely. For example, the React Team have advocated avoiding use of classes (to avoid dealing with instance state), and have developed ‘hooks’ to use  React without writing class components (You can read their motivations in the introductory blog here https://reactjs.org/docs/hooks-intro.html).

On the flip-side, some actors are further cementing the use of ‘this’. For example, to solve a problem with having a single keyword to refer to the top-level context object in both server-side and client-side JavaScript, the TC39 committee have pushed a proposal that introduces a ‘globalThis’ keyword. This generated a lot of debate (https://github.com/tc39/proposal-global/issues/31) and desperate attempts to find an alternative, which did not pan out.

Dealing with ‘this’ is probably not going away anytime soon, however if you have the knowledge outlined here, you shouldn’t run into much trouble.

If you’d like to go even deeper, you can dig into the ECMAscript spec on execution context to understand a bit about the formal definition of these behaviours https://ecma-international.org/ecma-262/5.1/#sec-11.1.1

Thanks for reading, keep an eye out for more in-depth JS blogs in the future.