The magic of the “this” keyword in JavaScript
Here’s a list of my best web development tutorials.
Complete CSS flex tutorial on Hashnode.
Ultimate CSS grid tutorial on Hashnode.
Higher-order functions .map, .filter & .reduce on Hashnode.
Follow me @ Twitter, Instagram & fb to never miss premium articles.
JavaScript borrows the this
keyword from C++, where it is used to point to an instance of an object from within its own class definition. There is absolutely no confusion about the this keyword in C++ because it is never used for any other intent than to point to an instance of an object from its constructor!
The original designer of JavaScript decided to tie a secondary feature to this. Not only is it used to point to an instance of an object from its own constructor or its methods (hence, this), but it is also used to keep track of execution context — which is often based on where a function was called from.
This duality of this is exactly what has wreaked havoc on learning and understanding how it works. It is also why this can be so confusing even to programmers who come to JavaScript from C++.
Overview
So let’s summarize for JavaScript:
- The this keyword is used to point to the instance of an object from its own constructor and methods (when used inside function or class scope.)
- The this keyword also keeps track of execution context also often referred to by some as the lexical scope or lexical environment. Think of lexical scope as location in memory allocated for all local variables to that scope.
- The link to the execution context can change: for example using this inside an arrow function is not the same as using this in an ES-style function. Arrow functions are not constructors and cannot be used to instantiate an object. So they don’t even have their own this context. But they do have this. So what does it point to? It is likely to be the parent context just outside of it.
- The link to execution context is also established when this is referred by a callback function, even if that function was defined inside an object’s constructor (when function or class is used as an object constructor.)
Where It All Begins
Let’s take a look at the original idea behind this:
function Cat() {
this.name = "felix";
console.log(this); // Cat {name: "felix"}
}let cat = new Cat(); // Cat {name: "felix"}
In this example function, Cat is used as a constructor for the object of type Cat. No confusion whatsoever. The this reference points to itself. I mean… this is why it’s called this in the first place: “this object” we’re defining right now.
When used as a constructor function, the body of the function itself becomes the object construction area.
As expected, here this points to self: Cat { name: “felix” }
After instantiating cat, cat.name will be “felix”, because property name was attached to a future object instance this. The same goes for class keyword:
class Mouse {
constructor() {
this.name = "mappy";
console.log(this); // Mouse {name: "mappy"}
}
}let mouse = new Mouse(); // Mouse{name: "mappy"}
Same thing is observed here. This makes perfect sense! If we just stopped here, the this keyword in JavaScript would never cause any confusion whatsoever.
This is how it works in C++ too. And I think the original designer of JavaScript should have stopped here. But… instead the this keyword was also chosen to carry a link to execution context. This choice was beyond reason. Why not invent another keyword: let’s say context, and separate two distinct functionalities? However, now this is responsible for both features!
So how does this “other” this work?
The Secondary Purpose Of the this Keyword
When used outside of object instances (previous examples) the this keyword takes on a completely new meaning.
Sometimes a function is just a function:
function abc() {
console.log(this);
}abc(); // [object Window] -- points to window object
Here, this points to [object Window] (or global scope.) Simply because it was called from the global scope (assuming we’re in <script> brackets.)
But…when you want to use the function as a constructor of an object, in other words, to create an object of type abc, the this keyword magically changes its context to the instance of the type object itself (no longer [object Window]):
function abc() {
console.log(this);
}let type = new abc(); // abc{} -- self
typeof type;
In other words, the object pointed to by the type variable (self) becomes the context. This is why this is called this.
It points to an object. Just not this object. In fact you could probably say it points to that object :-) And what that object is will also change based on the context from which a function was called.
Before arrow functions: that was exactly the name many programmers gave to the this object when used in another context.
First, let’s consider what the problem is:
function food(kind) {this.kind = kind;
this.cook = cook; // functions are hoisted, so it's perfectly
// fine to call or assign function names
// before the are defined.function cook(sec) {
setTimeout(function() {
console.log(this.kind + " cooked for " + sec + " seconds.");
}, sec * 1000);
}
}let soup = new food("soup"); // <--- this.kind = "soup"soup.cook(2); // undefined cooked for 2 seconds.
Wait. What? I thought we assigned this.kind = “soup”
when constructing the food object? Why does it say undefined cooked for 2 seconds?
When we called the setTimeout function, it disconnected us from the this keyword of the object. In callback of setTimeout, this points to [object Window] and not the original food object, aka [object Object].
In setTimeout, we assumed we’re accessing food.kind property. That’s what anyone would do. Because the function is called from food object. So, naturally this and this.kind should refer to that object, right? Wrong.
But this is such an obvious case. In fact, it almost seems that this should point to the object and not window. Well, that’s just how JavaScript works in this situation. So in the olden days, this problem was fixed as follows:
function food(kind) {this.kind = kind;
this.cook = cook; // functions are hoisted, so it's perfectly
// fine to call or assign function names
// before the are defined.function cook(sec) {// remember, in cook(), this still points to food object
// and not the window object (like in setTimeout)!
// this means we can still capture it here, and pass it
// into setTimeout callback manually, so let's make a
// reference to this and call it that. (or anything you want)let that = this;// Now inside setTimeout, refer to that.kind not this.kind:
setTimeout(function() {
console.log(that.kind + " cooked for " + sec + " seconds.");
}, sec * 1000);
}
}let soup = new food("soup");soup.cook(2); // soup cooked for 2 seconds.
Note the added let that = this; // seems like a copy of this object at first.
No copies are created in let that = this assignment. In JavaScript we don’t make copies when assigning variables. We create references. From now on that variable name will point to the original this object.
Remember functions created using the function keyword absorb all variables from the outside environment. So we can freely use that variable (that now points to food object) inside the callback function we passed to setTimeout.
What if this was addEventListener which also takes callback function? The same is true in this case. The problem with all callbacks inside functions is that they don’t link this to the object from which they are called.
The whole reason this was still [object Window] and not food, was because at the end of the day the method soup.cook(2) was really called from global scope (same as [object Window]). This secondary function of this is to carry context took precedence.
That’s not what we needed. So we force-fed that inside the callback and made it point to the food object’s this.
The link is established. Now calling soup.cook(2) correctly outputs the message “soup cooked for 2 seconds”. Should you want to eat such a soup is up to you. Let me know how that goes. We’re just learning JavaScript here ;-)
You hear a lot about “this binding” — but what in the world is it?
Well, that’s exactly the idea we’ve just covered above.
You have just created your own this binding!
Inside setTimeout, that is now bound to this. To bind this is simply to wire it to some object. In some cases we need it to be [object Window] in other cases we might need it to point to a custom object.
Good news is that since ES6 we no longer need to do any of this by hand: there are better ways of doing this. Exactly how is explained in the next section.
Arrow Functions To The Rescue
Before arrow functions, programmers had to bind this to that manually. But arrow functions can automatically fix this problem. Arrow functions have a “transparent” scope. In other words in setTimeout(()=>{this}, 1000) the this keyword does not point to [object Window]. It points to whatever is outside of it. And in the previous case, outside is the food object. This fixes the need to constantly have to bind this to the proverbial that.
And here is the modern version of the previous code without this/that binding:
function food(kind) {
this.kind = kind;
this.cook = cook;
function cook(sec) {
setTimeout(() => {
console.log(this.kind + " cooked for " + sec + " seconds.");
}, sec * 1000);
}
}
let soup = new food("soup");
soup.cook(2); // soup cooked for 2 seconds.
It works.
The let that = this nonsense is gone.
The soup and not undefined is cooked for 2 seconds!
For years programmers had to do this binding by hand. It’s annoying.
We replaced function() {} with arrow syntax () => {}
And that solved the whole problem. But at least we learned something new.
That’s why arrow functions were invented. (Although they also have other interesting properties: such as clean(er) code: when used together with Array’s higher-order functions.)
How to reconcile the two “this” use cases, without losing your mind
Well, this is the fun part. :)
There is a sane way of thinking about this in both contexts.
Remember how we saw that this points to instance of an object in first case?
But in the second case it points to some other context?
It all starts to make more sense if we recognize that context is also an instance of an object! Just not necessarily the one belonging to the scope we’re in. It is more like a link to the outside world. And the outside world is — roughly — the place from which the function was called.
In some cases JavaScript will decide for us whether to use the link to the outside world, or to use this as a reference to the instance of the object we are in the constructor’s scope of.
Takeaway: Context is always an instance to an object. Because when our JavaScript program begins, browser creates: var window = new Window() object behind the scenes. This is the very first “root” context created.
So when we are in <script> tags, we are actually inside the body of the Window constructor function. And this is why [object Window] is a prime candidate for being pointed to by this in many cases. Because this is the object from which many functions are called.
Every function call will stem from that context. Calling a function inside a function will carry this context, creating a stack of execution contexts.
So when does this *not point to [object Window]?
In the second use case when this points to the instance of an execution context rather than the instance of the object from constructor we’re in… what are the cases when this will not point to [object Window]?
That depends on whether the function was a callback, but also where the function that called the callback was called from.
Is this pure confusion or genius of JavaScript? I’ll let you decide.
Keep writing code.
These types of concepts take time to sink in.
In a lot of cases, you don’t even need to understand them in order to write good JavaScript code. But it’s probably in your interest to get to this level of detail because these types of questions are often asked in interviews.
Just write JavaScript and watch what happens. You will learn to understand this by instinct the more you practice.
If you need to do trial and error, go for it. Even seasoned developers still get confused about what this really points to at times.
If I missed something, or you know of some odd ball scenario that needs a mention, let me know and I can update this article.
Thanks for reading and hope this helped someone out there!