- The purpose of
this
is to provide (or maintain) context to a function. This
does not refer to the function itself.This
does refer to the owner of a function.This
uses a strict set of rules to determine its context.
The ambiguous this
Note: To maintain clarity (and sanity) I will always use
this
when referencing code. Everything else will be referred to as: this, “this,” or this.
The keyword this
is likely one of the most misunderstood (and misused) properties in all of JavaScript (JS). It’s no wonder either, considering the similarities it shares with its English counterpart – this. While they are both used for a similar purpose (providing context to something), how you derive that context is quite different.
Why we have this
If we break this
down to its most basic level, it simply allows us to use a single function on many different objects.
Here’s an example.
function getNames() {
return this.firstName + ' ' + this.lastName;
}
function nameList() {
let list = "Name: " + getNames.call(this);
console.log(list)
}
const names = {
firstName: "Bilbo",
lastName: "Baggins"
}
const moreNames = {
firstName: "Frodo",
lastName: "Baggins"
}
nameList.call(names)
// Output: Name: Bilbo Baggins
// because our context is “name”
nameList.call(moreNames)
// Output: Name: Frodo Baggins
// because our context changed to “moreNames”
Code language: JavaScript (javascript)
As you can see, we’re able to reuse these functions as many times as needed – all we have to do is change the context of the function itself. Without this
, we’d have to write a whole new function any time we want to pull a different name. Now imagine if we had to pull a few thousand names!
Mistakes with this
Before we dive too deeply into what this
is doing, let’s first take a moment to examine what it’s not doing.
Here’s an example of how failing to understand this
can leave people frustrated.
function coffee(number) {
console.log("Cups of coffee: " + number);
// Don’t do this
this.total++;
}
coffee.total = 0
let i;
for (let i = 0; i < 10; i++) {
if (i > 0) {
coffee(i);
}
}
// Cups of coffee: 1
// ...
// Cups of coffee: 9
// Great! It's keeping track of our coffee addiction.
console.log(coffee.total);
// Output = 0 ...wait what?
Code language: JavaScript (javascript)
While it would seem that this
should refer to the function itself, that isn’t the case. What’s really happening here is that this
is pointing to the owner of the function.
Naturally, your next question should be: “Then what is the owner of our function?”
If a function hasn’t been given any context (this will be covered later), the default owner of that function is the global scope
or window
of the program. As you can imagine, using the global scope
as a reference will cause all sorts of issues.
To give you an example of this, the above code has actually created a new global variable called total
, with a value of NaN
. Here it is action:
See the Pen Example of how to “not” use this. by yamato53 (@yamato53) on CodePen.
Note: You can prevent
this
from using theglobal scope
by addingstrict mode
to your code. Withstrict mode
enabled,this
will throw an error ofundefined
instead of interacting with the top level of the program.
At this point, most people will start looking for an easy solution – such as this:
The work-around “solution”
function coffee(number) {
console.log("Cups of coffee: " + number);
// we want this to keep track of our coffee intake
// so we provide a specific place to store our counter information.
coffeeCounter.total++;
}
let coffeeCounter = {
// then we create a “holding place” for the total.
total: 0
}
coffee.total = 0;
// coffee.total uses the coffee function to
// determine where it's saving the total number of cups at.
let i;
for (let i = 0; i < 10; i++) {
if (i > 0) {
coffee(i);
}
}
// Cups of coffee: 1
// ...
// Cups of coffee: 9
console.log(coffeeCounter.total);
// Output: 9
Code language: JavaScript (javascript)
This example is like finding our car is leaking oil and instead of patching the leak, we top off the oil tank and call it fixed.
Sure, “technically” it will work – but we’ve still got a big problem under the hood and we don’t when it’s going to break again.
Proper use of this
So with all that in mind, what’s the real solution to our coffee woes? We can use a built-in method named call()
to assign the proper context to our coffee
function.
function coffee(number) {
console.log("Cups of coffee: " + number);
this.total++;
}
coffee.total = 0 // tracks our coffee total
let i;
for (let i = 0; i < 10; i++) {
if (i > 0) {
coffee.call(coffee, i);
// forces the coffee function to maintain
// its context for this.count
}
}
// Cups of coffee: 1
// ...
// Cups of coffee: 9
console.log(coffee.total);
// Output: 9 - yay coffee
Code language: JavaScript (javascript)
We’ll learn more about call()
and a few other methods later on. For now, just know that JS does give us the tools we need to properly use this
.
What this means
The purpose of this
is surprisingly similar to the use of pronouns in normal languages. Here’s an example:
“Jill is eating her lunch.”
Here, we’ve set a clearly defined object (Jill), and then use a pronoun (her) to provide clarity to the sentence structure. Now compare that same sentence without the pronoun:
“Jill is eating Jill’s lunch.”
As you can see, without the help of proper pronouns, it’s difficult to tell what this example is even saying. Similarly, this
is used to provide specific context to JavaScript functions.
How to set the context of this
There are a few different ways we can set ownership, with the most straight-forward solution utilizing an object’s scope. Instead of messing with methods and other shenanigans, we can “contain” this
within the object we want to use.
Here’s how that looks in action:
const schoolbus = {
children: 20,
driver: 1,
// placing the function inside of an object automatically
// sets the context for this.
getTotal: function() {
let capacity = this.children + this.driver;
return capacity;
}
}
schoolbus.getTotal()
// Output: 21
Code language: JavaScript (javascript)
As you can see, we’ve given ownership to a function simply by placing it within the object. Because the function is “contained” it will always use that object as its context – this is referred to as using lexical scope.
Info: You’ll often see lexical scope used in a coding style known as “Principle of Least Privilege”. The purpose of this style is to “hide” anything from the global scope that isn’t completely necessary.
That being said, what if we’re working outside of the constraints of an object? Or maybe we want to create a function that can be reused on many different objects? Luckily, we have answers to those questions as well – but first, we need to understand a few more things about this
.
Why this can be confusing
Is this
starting to make sense? Great! Now it’s time to throw another curveball.
const restaurant = {
food: 'cheeseburgers',
menu: function() {
console.log(this.food)
}
}
restaurant.menu()
// ^ ^
// Context | Reference
// Output: cheeseburgers
Code language: JavaScript (javascript)
This code seems simple enough, if we break it down it’s doing three things:
- We have the main object
restaurant
. - We have a reference to the restaurant’s
menu
. - We’re given the menu’s
food
information – cheeseburgers.
Now, let’s go one step deeper.
‘use strict’ // enabling strict mode to catch any errors.
const restaurant = {
food: 'cheese burgers',
menu: function() {
console.log(this.food)
}
}
$('button').click(restaurant.menu)
// ^ ^ ^
// Context | References
// Output: Error - undefined
Code language: JavaScript (javascript)
Wait a sec, why is there an error?
To put it crudely – most native JS methods (such as .click
) do not literally pass in their arguments (restaurant
and menu
). Instead, it turns these items into a reference. Consequently, because restaurant
is being used as a reference instead of the main context, this
no longer knows what it’s supposed to do!
Think of it like saying “Someone is eating her sandwich.” – because we lost our main subject, we no longer know who “her” is referring to.
This begs the question – how do we retain context when using methods?
Methods for this
JavaScript provides us with four options we can use to give context to this
:
We’ll start with the new
operator first, as it as the highest seniority of the four.
New object binding
When an object is created using the new
operator, this
will always refer to the newly created object.
Here’s how that looks in practice:
function Computer(brand, type) {
this.brand = brand;
this.type = type;
}
const newComputer1 = new Computer('Dell', 'desktop')
const newComputer2 = new Computer('Apple', 'laptop')
console.log(newComputer1) // Output: { brand: 'Dell', type: 'desktop' }
console.log(newComputer2) // Output: { brand: 'Apple', type: 'laptop' }
Code language: JavaScript (javascript)
As you can see, any objects created with new
are always set as the main context. This allows us to use a single universal function (also known as a constructor) to build any number of objects.
Bind()
Where new
is used to create a new object, bind()
is used to create a new function, that has its this
value bound to the object we pass in. In other words, we’re saying “this
must always use the context we give it.”
First, let’s take a look at an incorrect example. Then, we’ll see how bind()
can be used to fix it.
const basket = {
apples: 2,
getApples: function() {
return `I have ${this.apples} apples!`
}
}
const showApples = basket.getApples
console.log(showApples())
// Output: "I have undefined apples!" Uh oh...
Code language: JavaScript (javascript)
Why won’t this lovely program return how many apples we have in our basket?
Simply put, showApples
is pulling getApples
out of its cozy basket object. Since the getApples()
function is no longer “inside” the basket
object, it loses its context.
However, with one simple line of code, we can solve our apple conundrum.
const basket = {
apples: 2,
getApples: function() {
return `I have ${this.apples} apples!`;
}
}
var showApples = basket.getApples.bind(basket)
console.log(showApples()) // Output: "I have 2 apples!"
Code language: JavaScript (javascript)
As you can see, the showApples()
function is now bound to our basket
object. We can call it as many times as we need, and it will always have the same context.
Call() and Apply()
If bind()
is meant for long-term use, then consider call()
and apply()
the short-term solution.
We can use these two methods if we need to do something with an object – but have no plan to reuse it in the future. In other words, the context provided to call
and apply
will be forgotten after being used.
Here’s an example:
function fruits() {
// .join is just adding spaces between each item in our array.
let answer = [this.fruitOne, "and", this.fruitTwo, "are my favorite
fruits!"].join(' ')
console.log(answer)
}
let favoriteFruits = {
fruitOne: 'apples',
fruitTwo: 'bananas'
}
fruits.call(favoriteFruits)
// Output: "apples and bananas are my favorite fruits!"
Code language: PHP (php)
Much like the previous two methods, call()
is providing our function with the right context for this
. The difference here is that call()
won’t waste resources by saving this to a new function or object.
Note:
Apply()
works exactly the same way ascall()
– the only difference beingcall()
accepts a list of arguments, whereasapply()
accepts an array of arguments. You can read more about this subtle difference from the Mozilla dev docs.
Some keynotes:
Bind()
is used to assign a specific owner to a function, no matter how many times that function is reused.Call()
andapply()
are meant to be used immediately. The context these methods provide can only be used in that specific instance.Bind
,call
andapply
can not be used together.
Take-away
As we’ve seen, this
doesn’t warrant all of the frustration that often comes with it. Much like a game of chess – if you don’t understand how each piece works, you’re going to have a hard time playing the game.
If you’re still a bit fuzzy on the whole subject, just focus on these four simple rules:
New
will always setthis
to the newly created object.Bind()
,call()
andapply()
will setthis
to the object you’ve specified.- Using
this
within an object forces it to use that object as the owner. - If none of the above apply,
this
will use theglobal scope
as its context (or throw anundefined
error if instrict mode
).
That’s it! Following these guidelines will keep you from getting tripped up by the (seemingly) strange behavior of this
.
If you’d like to take a deeper dive into the “hard parts” of JS (which I highly recommend you do!) check out this book series – You Don’t Know JS. You’ll come to appreciate how all these weird systems work together to create the strange language that we all love to hate – JavaScript!