Joel Hassan

This isn't so bad - Understanding the `this` Keyword in JavaScript

2019-06-10

jsthiskeyword


Introduction

JavaScript supports both functional and object-oriented paradigms, and this is a language feature where the two somewhat clash. The concept of 'this' or 'self' is not found in other functional languages, which can be confusing.

The actual this keyword does in JavaScript what it does in other languages - it references objects, providing access to their properties and methods. The potentially confusing part is the fact that the object this refers to in a given instance depends on how the function (utilising this) is called.

The reason lies in functions being executed in something known as an execution context, refering to the code's execution environment. This context is the value of this at any given instance, and the environment we are refering to can be either global, local or the inside of an eval function. The this keywords contains a value dependant on the environment or context the function is called in.

Prerequisites

Prior to figuring out how the this keyword works in JavaScript, you'll need to have the basics of objects, functions, and types of scope covered.

this - function and method contexts

The keyword always refers to a context object, i.e. the object it belongs to, be that the global window object, an owner object, a function object or an element in an event.

calling this in a method

Functions are methods when they're part of an object.

// method context

let someObject = {
    name: 'our object',
    logObject() {
    console.log(this === someObject); // output: true
  }
};

The above comparison evaluates to true, because this is referencing the object that the method belongs to. That is how it works in the context of methods.

// adding a new method to the object
someObject.newFunction = function() {
  console.log(this === someObject); // output: true
};

The above will also log true to the console. The execution context is still the object.

So, in a method, this references that object which it belongs to, i.e. itself, the owner object. The context here is localised to the object, it is no longer in the global (window) context.

Let's try assigning the method to a variable:

// assigning a method to a variable
let logFunction = someObject.logObject
logFunction() // false 

Here, this does not reference our object - it has lost context, and now references global. The 'logfunction' variable contains a regular function, not a method.

calling this in a regular function

Functions can also be regular, i.e. outside any object. In such cases, this refers to the global context. What do we mean by global context?

In browsers, the global context is the window object, whereas in Node.js the global scope is the Node.js module.exports object itself.

So, when executing regular functions, this references the global context by default.

Here's an example:

// global context

globalThree = 3;
function funct() {
  console.log(this); // output: window object
  console.log(this.globalThree); // output: 3
}
funct();

With the browser the above is equivalent to:

globalThree = 3;
function funct() {
  console.log(window); // output: window object
  console.log(window.globalThree); // output: 3
}

calling a function using the new operator

Another context involves calling a function using the new constructor.

What the new keyword does is:

  1. Create a blank object => {}
  2. Set this object to another object through an assignment
  3. Make this point to the blank object

Now, when this is used in construction functions, the context is the newly-created object, not the global one. this will reference an empty object. Arguments passed to the function can be set as the new object's properties using this.

An example:

// constructor function

function Film(title) {
  this.title = title; // i.e. set the title paramater as the title of this object 
  console.log(this); // does not output the window object
}

const newFilm = new Film('some film');
// logs the newFilm object instead of the window object

this & functions within objects

As mentioned, the this in a regular (non-method) function references the browser window context. It's common, for example, to pass callback functions to our class methods themselves. What does this reference within those callback functions?

It happens to be that this loses context in certain situations, such as within callback functions.

Let's look again at the film object example and extend it.

// using a regular callback
let film = {
  title: 'a good film',
  ratings: [3, 4, 5, 4],
  showRatings() {
    this.ratings.forEach(function(rating) {
      console.log(this.title, rating); // this here references the global (window) context
    });
  }
};

Here, the function that takes rating as a parameter has nothing to do with the object. As a result, it will reference the global context and output:

undefined 3
undefined 4
undefined 5
undefined 4

We can make it work by using the optional 2nd argument of the forEach method.

let film = {
  title: 'a good film',
  ratings: [3, 4, 5, 4],
  showRatings() {
    this.ratings.forEach(function(rating) {
      console.log(this.title, rating);
    }, this); // passing this (the film object) as a 2nd argument
  }
};

Now the output is:

a good film 3
a good film 4
a good film 5
a good film 4

Since we passed this as an argument to the callback it is able to reference it and we get the desired result.

That can be avoided that by using ES6 arrow functions, which behave differently here. this in arrow functions retains the value of the enclosing lexical context's this. Or, in simpler terms, an arrow function called within an object will have its this refer to that object. This is one way of controlling context.

// using an arrow function
let film = {
  title: 'a good film',
  ratings: [3, 4, 5, 4],
  showRatings() {
    this.ratings.forEach(rating => {
      console.log(this.title, rating); // this here references the film object
    });
  }
};

binding the value of this

Losing track of this can cause painful bugs within programs. Remember, the value of this is determined by how the function is called.

Sometimes we want to keep the value of this constant, regardless of the context - this is were binding comes in. Binding can be achieved in several ways; we saw that the use of arrow functions is one.

Another one is the bind() method introduced in ES5. The object that we want to be bound to the function is passed as an argument to it, and the (initially) undefined this becomes the this of the object (that was passed).

Carrying on with the film class, if we wanted to use the showRatings() method with this bound to the class.

// a simple film class
let film = {
  title: 'a good film',
  ratings: [3, 4, 5, 4],
  logTitle: function() {
    console.log('Title:', this.title);
    }
}

// the following will not work
setTimeout(film.logTitle, 1000) // output: Title: undefined

// the following, however, will work
setTimeout(film.logTitle.bind(film), 1000) // output: Title: a good film

In the second scenario, what is being passed to the setTimeout is a new function returned by bind() that has bound this to the film object.

Other tools that can be used to deal with context issues are the that/self pattern and arrow functions seen in the example.

Conclusion

To sum:

  1. In a method, this references the object to which the method belongs
  2. In a non-method function, this references the global context - window object in a browser
  3. In construction functions, this is set to point to the newly created object
  4. this can lose context, e.g. in callback functions
  5. The bind() methid and other tools enable us to keep track of context

Many argue against the use of this alltogether, but it's likely that you'll need to use it at some point. Regardless, I hope that this post made it somewhat clearer.