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.