Learn this Keyword in JavaScript the Easy Way
What is the infamous this in JavaScript, implicit and explicit binding? Learn about it in this post.
π¨ Be cautious - Donβt compare this
keyword of JavaScript with other programming languages. The working of JavaScript is inherently different from that of object-oriented programming languages like C++, Java, OR π Python.
Letβs start with the anatomy of this
.
Anatomy of this
A functionβs this
keyword refers to the execution context for that function call. Note that, the function call and not the function was defined. π¨ It doesnβt matter how the function was defined, what will be this
keyword entirely depends on how the function was called.
Therefore, a this
aware function can have a different context each time itβs called. This is amazing. It makes this
keyword very flexible and reusable. Itβs somewhat JavaScriptβs version of dynamic scope. Read more about the difference between lexical scope and dynamic scope in the Lexical Scope section of Get an in-depth view of Scopes in JavaScript article.
Consider the following code:
function eat(person) {
console.log(`${person} is eating ${this.food}`);
}
function restaurant() {
var foodContext = {
food: "πππ",
};
eat.call(foodContext, "John");
}
restaurant(); // John is eating πππ
Here, eat
is a this
aware function. In the restaurant
function, we use the call
method on eat
function for binding the this
keyword of eat
with a context which in this case is foodContext
. This is called hard binding (more on this later in the post).
Since during the call, eat
functionβs this
refers to the foodContext
, we have this.food
as πππ
. This is mind-blowing because now you can use the eat
function with multiple contexts. π₯ this
provides flexibility that lexical scopes donβt provide.
function eat(person) {
console.log(`${person} is eating ${this.food}`);
}
function restaurant() {
var breakfast = { food: "πππ" };
eat.call(breakfast, "Breakfast");
var lunch = { food: "π₯π₯π₯" };
eat.call(lunch, "Lunch");
var dinner = { food: "π₯π₯π₯" };
eat.call(dinner, "Dinner");
}
restaurant();
// Output:
// Breakfast is eating πππ
// Lunch is eating π₯π₯π₯
// Dinner is eating π₯π₯π₯
What is hard binding? Well, there are 4 different ways of invoking a function and each one of them will give a different this
context.
Implicit Binding
What happens when you donβt explicitly bind this
keyword to a context?
// Namespace pattern
var restaurant = {
food: "πππ",
eat(person) {
console.log(`${person} is eating at ${this.food}`);
},
};
restaurant.eat("π΅"); // π΅ is eating at πππ
Here, this.food
is πππ
not because the eat
function is inside the restaurant
object, so this
points to the restaurant
. Itβs because, in the call site, eat
is invoked with the restaurant
object.
The detail that this
keyword depends on how the function is called (manner of the function call) and not how itβs defined is very important because if we were to use a callback in this scenario then the call site would be different and hence this
keyword would be pointing on a different object.
// Namespace pattern
var restaurant = {
food: "πππ",
eat(person) {
console.log(`${person} is eating at ${this.food}`);
},
};
function personEating(person, callback) {
callback(person);
}
personEating("π΅", restaurant.eat);
Here, this
keyword is pointing to the personEating
context. So if we had [this.food](http://this.food)
inside the personEating
function then this.food
wonβt be undefined
. For example:
function anotherPersonEating(person, callback) {
this.food = "πππ";
callback(person);
}
anotherPersonEating("π΅", restaurant.eat); // π΅ is eating at πππ
This raises another issue while using callbacks, we lose our this
. This issue is solved by hard binding. Having implicit binding helps to share methods (behavior) among different contexts. Itβs an intentional tradeoff between predictable and flexible. To share behaviors letβs learn about explicit binding.
Explicit Binding
We can use call OR apply methods on a function to explicitly tell JavaScript, which context to invoke the function in.
function eat(person) {
console.log(`${person} is eating ${this.food} in ${this.name}`);
}
var mcDonalds = {
name: "McDonalds",
food: "πππ",
};
var pizzaHut = {
name: "Pizza Hut",
food: "πππ§",
};
// Using .call
eat.call(mcDonalds, "John");
// John is eating πππ in McDonalds
// Using .apply
eat.apply(pizzaHut, ["John"]);
// John is eating πππ§ in Pizza Hut
A variation of explicit binding is hard binding. Here we solve the issue of losing this
binding. This issue commonly happens when working with callbacks. Read more on this in the Implicit Binding section.
var pizzaHut = {
name: "Pizza Hut",
food: "πππ§",
eat(person) {
console.log(`${person} is eating ${this.food}`);
},
};
setTimeout(pizzaHut.eat, 1000, "π΅"); // π΅ is eating undefined
What happens is that the call site is different, it is inside the setTimeout
, and depending on its call site our this
behaves because of which we get undefined
(since this.food
wonβt be defined there). To solve this issue we use the bind
method on a function which will produce a new function that will be bound to the context we provide. π¨ This will take away the flexibility offered by this
keyword.
Also, note that apply
and call
will also give the same results but instead of returning a new function, they will just execute the function in hand. π On a side note, the setTimeout
utility is defined by HTML, itβs not invoking it just with the default call, it actually explicitly invokes it with a .call
in the context of global. So the this
points to the global object for the setTimeout
.
π¨ Note on the flexibility provided by this
keyword
If we were to go to all the trouble to define a bunch of functions on some namespace object and have this
in front of every property reference and every method access, AND then if all of your function calls use the bind
, then we would be cutting yourself off at the knees β.
This is because you put all the this
keywords for its dynamic feature and then use bind
to remove that dynamic feature. A better thing to do in this case will be to write a module that uses closure and has a fixed, predictable behavior.
Conclusion
this
keyword provides flexibility that lexical scope and closures donβt. We can opt-out of this flexibility by hard binding but be cautious to not overdo it because it dilutes the purpose of using this
.
This is the end of this post but not for this
keyword top. There are some topics such as the new
keyword, default binding, binding precedence, and this
inside an arrow function. So to not miss anything do π₯ follow me and if you like the post then donβt forget to give it a ππΌ.