Severin Perez

What in the World is this?!

June 09, 2018

If you’re like me, then one of your first thoughts when you started reading JavaScript code was “why in the world is this written all over the place?!” It is a strange syntax indeed, and confusing to read about because “this” is one of the most common words in the English language! Rest assured though, with a little practice you’ll master the necessary mental gymnastics to understand this. So, let’s get down to it and figure out what in the world this really is.

Identifying this

Your first task in learning about the this keyword is to identify what it represents in different places in your code. Put simply, this is the current execution context of a JavaScript function at any given point in your program. Depending on how the function is defined and invoked, the context associated with the this keyword can change dramatically, thereby producing dramatically different outputs. It is therefore important to understand how this works so that you can: a) accurately predict how a given function will execute; and, b) invoke the same function in different execution contexts, thus minimizing code repetition.

Let’s get one thing out of the way first: this is not the scope of a function and it is not the function itself. Scope and execution context are related concepts but they do not line up perfectly, nor do they always change at the same time. It’s better to think of execution context on its own and take the time to understand the rules of how it changes.

Speaking of the rules of execution context, let’s look at them one by one.

Default / Function Invocation Execution Context

By default, if no other context rules apply, a function’s execution context is the global object, which in the browser environment is the window object. Try entering this in your browser’s console and take a look at the return value:

> this
=> Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}

So what does this mean in practice? It means that if you use the this keyword in a function where no other context rules apply, it will always refer to the global object. Let’s try it with a simple function that uses the this keyword to set some value.

function setMovie(movie) {
  this.movie = movie;
}

setMovie("Raiders of the Lost Ark");

console.log(window.movie);  // "Raiders of the Lost Ark"
console.log(this.movie);    // "Raiders of the Lost Ark"
console.log(movie);         // "Raiders of the Lost Ark"

As we can see, by calling our function setMovie, we have effectively created a property called movie on the global object. Now, this is a bit of a silly example because it doesn’t do much of value (and you almost certainly should not be creating global variables like this, or really at all); however, it shows us that by default, this is the global object.

It is very tempting to think “oh, by default this is set to the current scope” (and indeed, it looks like that in the previous example doesn’t it?) but consider the following:

function setEntertainment(type, value) {
  function setMovie(movie) {
    this.movie = movie;
  }
  
  if (type === "movie") {
    setMovie(value);
  }
}

setEntertainment("movie", "Jurassic Park");

console.log(window.movie);  // "Jurassic Park"
console.log(this.movie);    // "Jurassic Park"
console.log(movie);         // "Jurassic Park"

Here, the function setEntertainment calls a locally scoped function, setMovie, which in turn uses the this keyword to set the property movie at the applicable execution context. After invoking setEntertainment, we log the value of window.movie and this.movie from global scope and see that it is indeed "Jurassic Park". If setMovie was using the current scope, rather than the current execution context, then it would have set the property movie inside the scope of setEntertainment rather than in global scope.

Method Execution Context

We now know how this resolves when no other rules apply, but that doesn’t help us much. To make use of the power of this, we need to start applying rules. The first of these is method invocation. A method is a function that is defined as a property on an object. When a method is invoked, the value of this is the calling object, as in the following snippet.

function setMovie(movie) {
  this.movie = movie;
}

var theater = {
  loadProjector: setMovie,
};

theater.loadProjector("The Princess Bride");

console.log(theater.movie);   // "The Princess Bride"
console.log(window.movie);    // undefined

In this snippet, we define an object called theater and assign our setMovie function (which is still declared in global scope like before) to the theater object’s loadProjector property. We then use method invocation to execute loadProjector (which is the same function as setMovie), but this time, it does so in the execution context of theater rather than the global object. When we log the value of the theater.movie property, we can see that it has been successfully set to "The Princess Bride"; however, when we try to log the value of window.movie, as we did before, we get undefined. Here we can see that because setMovie was called via the loadProjector property, the value of this was the calling object, theater.

Explicit Execution Context

Part of what makes the this keyword so powerful is that it allows us to DRY (“don’t repeat yourself”) up our code and avoid repeating function definitions over and over. What if, for example, we wanted to call one object’s method from the context of another object? Well, thanks to the call() and apply() methods, we can do just that! Any function you write inherits these methods from Function.prototype and they can be used to explicitly bind a method call to the context of some object, which is supplied as the first argument to these methods. In practice, this means that you can tell a function what execution context to use, effectively changing the meaning of any internal uses of the this keyword. Let’s look at an example.

function setMovie(movie) {
  this.movie = movie;
}

var theater = {
  loadProjector: setMovie,
};

var television = {};

theater.loadProjector.call(television, "Top Gun");

console.log(television.movie);  // "Top Gun"
console.log(theater.movie);     // undefined

At first glance, this snippet looks a lot like the previous one, except that when we invoke the theater.loadProjector method, we do so using the call() method inherited from Function.prototype. We pass the call() method a new object, television, to use as the execution context, as well as an argument to use in the original method. When we log the values of television.movie and theater.movie respectively, we see that television.movie has been set to "Top Gun" but theater.movie is undefined.

But wait a minute, last time we called the theater.loadProjector method it set a new property on its calling object! What happened?! The difference is that this time we passed an explicit execution context for the method to use. As a result, the this keyword in the ultimate method definition was set to the television object rather than the theater object—all thanks to our use of call(). Of note, we could have used apply() as well, which achieves the same result but accepts additional arguments in the form of an array rather than one after another the way call() does.

Bound Execution Context

The ability to call a method in a variety of execution contexts is incredibly useful, but sometimes you want a method to use one execution context and only one execution context, no matter what. This leads us to the bind() method, which is also defined on Function.prototype. Unlike call() and apply() though, bind() is not dynamic. Once you have used it to bind a function to some execution context, that execution context cannot change. Consider the following snippet:

function setMovie(movie) {
  this.movie = movie;
}

var theater = {
  loadProjector: setMovie.bind(theater),
};

var television = {};

theater.loadProjector.call(television, "Top Gun");

console.log(television.movie);  // undefined
console.log(theater.movie);     // "Top Gun"

This snippet is exactly the same as the previous one, with one small (but vital) difference. On line 6, when we define the loadProjector method on the theater object, we do so using bind(), passing in the theater object itself as the bound execution context. On line 11, when we attempt to call the theater.loadProjector method in the context of the television object, we now get a completely different result than before. Now, despite our attempt at explicitly setting the execution context, our method sets the movie property on theater rather than television. This is because we permanently bound the method to the theater object and it therefore uses the theater object as the execution context, no matter what.

Constructor Execution Context

Constructor functions, which are invoked using the new keyword, are a bit confusing at first, and to make them even more confusing they have a special means of defining execution context. When a constructor function is called it creates a brand new object and assigns that object as its execution context. Assuming no other value is explicitly returned, this new object is returned by default. Any references to this inside the constructor function are a reference to the new object and you can set its properties as in the following snippet.

function Movie(title, director) {
  this.title = title;
  this.director = director;
}

var independenceDay = new Movie("Independence Day", "Roland Emmerich");

console.log(independenceDay.title);     // "Independence Day"
console.log(independenceDay.director);  // "Roland Emmerich"

As you can see, our new variable independenceDay was assigned the values we provided for its title and director properties. By using the new keyword we used the function as a constructor, thus creating a new object, which we then accessed from inside the constructor function using the this keyword, set properties on, and returned by default. If we had not used the new keyword then this would have referred to the default execution context and our new properties would have been set on the global object instead, as in our very first snippet.

Context Loss

Before we look at one last type of execution context, let’s take a brief detour and consider the problem of context loss. Because execution context is a question of how and where a function is called, rather than purely lexical rules (as with scope), context binding can sometimes result in surprising behavior. For example, take a look at the output in the following snippet:

function setMovie(movie) {
  this.movie = movie;
}

var theater = {
  loadProjector: setMovie,
  
  playMovie: function(movie, previewLength) {
    this.loadProjector(movie);
    
    setTimeout(function() {
      console.log("Now playing: " + this.movie);
    }, previewLength);
  }
};

theater.playMovie("Gremlins", 1000);  // "Now playing: undefined"

We have added a method, playMovie, to our theater object, which calls the loadProjector method and then uses setTimeout to wait some specified amount of time (the length of the previews) before playing the loaded movie. But, there’s a problem! The anonymous function used inside setTimeout is not invoked as a method and therefore defaults to the global object for its execution context. When it goes to look for this.movie, it gets a value of undefined instead of the movie held on the theater object.

There are a number of ways to solve the context loss problem, including binding the anonymous function inside setTimeout to the appropriate context, or saving the appropriate context in a variable (typically called self or that) and using it to access the movie property, as in the following:

// option 1
var theater = {
  playMovie: function(movie, previewLength) {
    this.loadProjector(movie);
    
    setTimeout(function() {
      console.log("Now playing: " + this.movie);
    }.bind(this), previewLength);
  }
}

// option 2
var theater = {
  playMovie: function(movie, previewLength) {
    var self = this;
    
    this.loadProjector(movie);
    
    setTimeout(function() {
      console.log("Now playing: " + self.movie);
    }, previewLength);
  }
}

Lexical this and Arrow Functions

To finish up our review of execution context in JavaScript, let’s look at a feature added in ES6 syntax: arrow functions. At first glance arrow functions may just seem like a convenient shorthand for defining functions, but they treat execution context differently than a normal function. In short, arrow functions use their enclosing lexical context as this rather than using normal function / method invocation context rules. Let’s see this in action using the context loss problem we just looked at.

function setMovie(movie) {
  this.movie = movie;
}

var theater = {
  loadProjector: setMovie,
  
  playMovie: function(movie, previewLength) {
    this.loadProjector(movie);
    
    setTimeout(() => {
      console.log("Now playing: " + this.movie);
    }, previewLength);
  }
};

theater.playMovie("Gremlins", 1000);  // "Now playing: Gremlins"

Unlike our earlier example, we now get the correct output of “Now playing: Gremlins” rather than “Now playing: undefined”. Note how on line 11 we are using an ES6 arrow function inside setTimeout rather than a normal anonymous function definition. Whereas the previous anonymous function in setTimeout defaulted to the global object for its execution context, the arrow function uses lexical this, which is the this inside the enclosing function—in this case, the theater object’s playMovie method. As we know from method invocation context rules, this inside theater.playMovie is the theater object itself, and we therefore get the correct output when accessing this.movie.

TL;DR

The this keyword in JavaScript is the execution context of a given function at a given point in your code. this is a question of how and where a function is invoked and is not synonymous with scope or with the function itself. By default, if no other context rules apply, this refers to the global object (window in the browser environment). However, if a function is invoked as a method, meaning it is a property on some object and invoked as such, then this is the calling object. There are also ways to explicitly declare which execution context a function should use, including the call() and apply() methods on Function.prototype, which take an execution context as their first argument. Similarly, the bind() method can permanently bind an execution context to a function. Separately, constructor functions have a unique execution context, namely, the object that they create when using the new keyword. In addition, you should always be cognizant of the problem of context loss, which occurs most often when using a callback that has a different execution context then the method invoking it. There are several ways to get around context loss, including binding callbacks or storing the appropriate execution context in a variable. Another way to deal with the problem is to use an ES6 arrow function, which binds an anonymous function to its enclosing lexical this.


Note: This article was originally published on Medium.


You might enjoy...


© Severin Perez, 2021