The Final Steps to Mastering JavaScript’s “this” Keyword
In a previous article we learned the fundamentals of using JavaScript’s this
keyword properly. We saw that the crucial factor in determining what this
refers to, is to find out the current execution context. However, this task can be a bit tricky in situations where the context gets changed in a way we don’t expect. In this article I will highlight when this might happen and what we can do to remedy it.
Fixing Common Issues
In this section we’ll explore some of the most common issues arising from the use of the this
keyword and we’ll learn how to fix them.
1. Using this
in Extracted Methods
One of the most common mistakes that people make is when trying to assign an object’s method to a variable and expecting that this
will still point to the original object. As we can see from the following example, that simply doesn’t work.
var car = {
brand: "Nissan",
getBrand: function(){
console.log(this.brand);
}
};
var getCarBrand = car.getBrand;
getCarBrand(); // output: undefined
Even though getCarBrand
appears to be a reference to car.getBrand()
, in fact, it’s just another reference to getBrand()
itself. We already know that the call-site is what matters in determining the context, and here, the call-site is getCarBrand()
, which is a plain and simple function call.
To prove that getCarBrand
points to a baseless function (one which isn’t bound to any specific object), just add alert(getCarBrand);
to the bottom of the code and you’ll see the following output:
function(){
console.log(this.brand);
}
getCarBrand
holds just a plain function, which is no longer a method of the car
object. So, in this case, this.brand
actually translates to window.brand
, which is, of course, undefined
.
If we extract a method from an object, it becomes a plain function again. Its connection to the object is severed, and it no longer works as intended. In other words, an extracted function is not bound to the object it was taken from.
So how can we remedy this? Well, if we want to keep the reference to the original object, we need to explicitly bind the getBrand()
function to the car
object when we assign it to the getCarBrand
variable. We can do this by using the bind() method.
var getCarBrand = car.getBrand.bind(car);
getCarBrand(); // output: Nissan
Now, we get the proper output, because we successfully redefine the context to what we want it to be.
2 this
Used in Callbacks
The next issue occurs when we pass a method (that uses this
as a parameter) to be used as a callback function. For example:
<button id="btn" type="button">Get the car's brand</button>
var car = {
brand: "Nissan",
getBrand: function(){
console.log(this.brand);
}
};
var el = document.getElementById("btn");
el.addEventListener("click", car.getBrand);
Even though we use car.getBrand
, we actually only get the function getBrand()
which is attached to the button
object.
Passing a parameter to a function is an implicit assignment, so what happens here is almost the same as in the previous example. The difference is that now car.getBrand
is not explicitly assigned, but implicitly. And the result is pretty much the same—what we get is a plain function, bound to the button
object.
In other words, when we execute a method on an object, which is different from the object upon which the method was originally defined, the this
keyword no longer refers to the original object, rather to the object that invokes the method.
With reference to our example: we are executing car.getBrand
on el
(the button element), not the car
object, upon which it was originally defined. Consequently, this
no longer refers to car
, rather to el
.
If we want to keep the reference to the original object intact, again, we need to explicitly bind the getBrand()
function to the car
object by using the bind()
method.
el.addEventListener("click", car.getBrand.bind(car));
Now, everything works as expected.
3 this
Used Inside Closures
Another instance when this
‘s context can be mistaken is when we use this
inside of a closure. Consider the following example:
var car = {
brand: "Nissan",
getBrand: function(){
var closure = function(){
console.log(this.brand);
};
return closure();
}
};
car.getBrand(); // output: undefined
Here, the output we get is undefined
, because closure functions (inner functions) don’t have access to the this
variable of outer functions. The net result is that this.brand
is equal to window.brand
, because this
in inner functions is bound to the global object.
To fix this issue, we need to keep this
bound to the getBrand()
function.
var car = {
brand: "Nissan",
getBrand: function(){
var closure = function(){
console.log(this.brand);
}.bind(this);
return closure();
}
};
car.getBrand(); // output: Nissan
This binding is equivalent to car.getBrand.bind(car)
.
Another popular method to fix closures, is to assign the this
value to another variable, thus preventing the unwanted change.
var car = {
brand: "Nissan",
getBrand: function(){
var self = this;
var closure = function(){
console.log(self.brand);
};
return closure();
}
};
car.getBrand(); // output: Nissan
Here, the value of this
can be assigned to _this
, that
, self
, me
, my
, context
, an object’s pseudo name, or whatever else works for you. The main point is to keep a reference to the original object.
ECMAScript 6 to the Rescue
In the previous example we saw a primer on what is known as “lexical this
“—when we set the this
value to another variable. In ECMAScript 6 we can use the similar, but more elegant, technique, applicable via the new arrow functions.
Arrow-functions are created not by the function
keyword, but by the so-called “fat arrow” operator (=>
). Unlike regular functions, arrow functions take the this
value from their immediate enclosing scope. The lexical binding of an arrow function can’t be overridden, even with the new
operator.
Let’s now see how arrow function can be used to substitute the var self = this;
statement.
var car = {
brand: "Nissan",
getBrand: function(){
// the arrow function keeps the scope of "this" lexical
var closure = () => {
console.log(this.brand);
};
return closure();
}
};
car.getBrand(); // output: Nissan
What You Need to Remember About this
We saw that the this
keyword, like every other mechanism, follows some simple rules, and if we know them well, then we can use that mechanism with more confidence. So, let’s quickly recap what we have learned (from this and from the previous article):
this
refers to the global object in the following cases:- in the outermost context, outside of any function block
- in functions that are not methods of objects
- in functions that are not object constructors
- When a function is called as a property on a parent object,
this
refers to the parent object. - When a function is called using
call()
orapply()
, orbind()
,this
refers to the first argument passed to these methods. If the first argument isnull
or not an object,this
refers to the global object. - When a function is called with the
new
operator,this
refers to the newly created object. - When an arrow function (introduced in ECMAScript 6) is used,
this
relies on lexical scope and refers to the parent object.
Knowing these straight and simple rules, we can easily predict what this
will point to, and if it’s not what we want, we know which methods we can use to fix it.
Summary
JavaScript’s this
keyword is a tricky concept to master, but with enough practice, master it you can. I hope that this article and my previous article, serve as good basis for your understanding and prove to be a valuable reference the next time this
is causing you headaches.