Mastering JavaScript fully is a lengthy journey. The this keyword is a very important concept in JavaScript, and also a particularly confusing one to both new developers and those who have experience in other programming languages.
In JavaScript, this
is a reference to an object. The object that this
refers to can vary, implicitly based on whether it is global, on an object, or in a constructor, and can also vary explicitly based on usage of the Function prototype methods bind, call, and apply.
You may have come across this
on your journey as a JavaScript Developer. When I started out, I first saw it when using eventListeners
and with jQuery. Later on, I had to use it often with React and I am sure you will do as well. The question is how to fully take control of it.
Explaining this
can lead to a lot of confusion 😕, simply by the naming of the keyword.
🛑 ⚠️ Important to remember is that this
is tightly coupled to what context you are in, in your program. Let’s start all the way at the top. In our browser, if you just type this
in the console, you will get the window-object
, the outermost context for your JavaScript. In Node.js, if we do:
console.log(this)
we end up with{}
, an empty object. This is a bit weird, but it seems like Node.js behaves that way. If you do
(function() {
console.log(this);
})();
You will receive the global object
, the outermost context. In that context setTimeout
, setInterval
, are stored. Feel free to play around a little bit with it to see what you can do with it. As from here, there is almost no difference between Node.js and the browser. I will be using window. Just remember that in Node.js it will be the global object, but it does not really make a difference.
To understand this
keyword, only we need to know how, when and from where the function is called, does not matter how and where function is declared or defined.
🛑 Remember: Context only makes sense inside of functions
There are four main contexts in which the value of this
can be implicitly inferred:
- the global context
- as a method within an object
- as a constructor on a function or class
- as a DOM event handler
Global
In the global context , this refers to the global object. When you're working in a browser, the global context is window. When you're working in Node.js, the global context is global.
For example you write a program without nesting anything in functions. You would simply write one line after another, without going down specific structures. That means you do not have to keep track of where you are. You are always on the same level.
When you start having functions, you might have different levels of your program and this represents where you are, what object called the function.
You'll almost always use this
in the context of a function, but just remember that if this is used in the global context, then it points to the global object (e.g.: window in the browser and global in Node.js).
The value of this differs depending on how a function is invoked (the call site), so we can’t know the value of this just by looking at the function itself, but we need to know the context in which the function is invoked.
🛑 Important is to keep track of the caller object
Let’s have a look at the following example and see how this changes depending on the context:
const cake = {
strong: delicious,
info: function() {
console.log(`The cake is ${this.strong ? '' :
'not '}delicious`)
},
}
cake.info() // The cake is delicious
Since we call a function that is declared inside the cake object, the context changes to exactly that object. We can now access all of the properties of that object through this
. In the example above, we could also just reference it directly by doing cake.strong . It gets more interesting, when we do not know what context, what object, we are in or when things simply get a bit more complex. Have a look at the following example:
const pastry = [
{
name: 'Muffin',
delicious: true,
info: function() {
console.log(`${this.name} is ${this.delicious ? '' : 'not '} delicious.`)
},
},
{
name: 'Choko Dream',
delicious: false,
info: function() {
console.log(`${this.name} is ${this.delicious ? '' : 'not '} delicious.`)
},
},
]
function pickRandom(arr) {
return arr[Math.floor(Math.random() * arr.length)]
}
pickRandom(pastry).info()
Classes and Instances
Classes can be used to abstract your code and share behavior. Always repeating the info function declaration in the last example is not good. Since classes and their instances are in fact objects, they behave in the same way. One thing to bear in mind is that declaring this in the constructor actually is a prediction for the future, when there will be an instance.
class Cake {
constructor(strawberry) {
this.strawberry = !!strawberry
}
info() {
console.log(`This cake is ${this.strawberry ? '' : 'not '}strawberry`)
}
}
const strawberryCake = new Cake(true)
const chocolateCake = new Cake(false)
strawberyCake.info() // This cake is strawberry
chocolateCake.info() // This cake is not strawberry
An Object Method
A method is a function on an object, or a task that an object can perform. A method uses this to refer to the properties of the object.
const capital = {
name: 'Berlin',
yearFounded: 1237,
describe : function() {
console.log(`${this.name} was founded in ${this.yearFounded}.`)
},
}
capital.describe()
In this example, this is the same as capital.
const capital = {
name: 'Berlin',
yearFounded: 1237,
details: {
symbol: 'bear',
currency: 'Euro',
printDetails() {
console.log(
`The symbol is the ${this.symbol} and the currency is ${this.currency}.`,
)
},
},
}
capital.details.printDetails()
🤔 Another way of thinking about it is that this refers to the object on the left side of the dot when calling a method.
A Function Constructor 🧐 🤐
When you use the new keyword, it creates an instance of a constructor function or class. Function constructors were the standard way to initialize a user-defined object before the class syntax was introduced in the ECMAScript 2015 update to JavaScript. In Understanding Classes in JavaScript, you will learn how to create a function constructor and an equivalent class constructor.
function countryCapital(name, yearFounded) {
this.name = name
this.yearFounded = yearFounded
this.describe = function () {
console.log(`${this.name} was founded in ${this.yearFounded}.`)
}
}
const berlin = new countryCapital('Berlin', 1237)
capital.describe()
🧐 In this context, this is now bound to the instance of countryCapital
, which is contained in the berlin constant
.
A DOM Event Handler
In the browser, there is a special this context for event handlers. In an event handler called by addEventListener, this will refer to event.currentTarget. More often than not, developers will simply use event.target or event.currentTarget as needed to access elements in the DOM, but since the this reference changes in this context, it is important to know.
In the following example, we'll create a button, add text to it, and append it to the DOM. When we log the value of this within the event handler, it will print the target.
const button = document.createElement('button')
button.textContent = 'Click me'
document.body.append(button)
button.addEventListener('click', function (event) {
console.log(this)
})
Once you paste this into your browser, you will see a button appended to the page that says "Click me". If you click the button, you will see Click me appear in your console, as clicking the button logs the element, which is the button itself. Therefore, as you can see, this refers to the targeted element, which is the element we added an event listener
to.
In a nested object, this
refers to the current object scope of the method.
Explicit Context
It is difficult to define exactly when to use call, apply, or bind, as it will depend on the context of your program. bind can be particularly helpful when you want to use events to access properties of one class within another class.
For example, if you want to write a simple game, you might separate the user interface and I/O into one class, and the game logic and state into another. Since the game logic would need to access input, such as key press and click, you would want to bind the events to access the this value of the game logic class.
🛑 The important part is to know how to determine what object this refers to, which you can do implicitly with what you learned in the previous sections, or explicitly with the three methods you will learn next.
Apply and call
They both do basically the same thing, only the syntax is different. For both, you pass the context as first argument. apply
takes an array for the other arguments, while call
simply separate other arguments
by comma.
What do they do? Both of these methods set the context for one specific function call. When calling the function without call
, the context is set to thedefault context
(or even a bound context
). Here is an example:
class Salad {
constructor(type) {
this.type = type
}
}
function showType() {
console.log(`The context's type is ${this.type}`)
}
const fruitSalad = new Salad('fruit')
const greekSalad = new Salad('greek')
showType.call(fruitSalad) // The context's type is fruit
showType.call(greekSalad) // The context's type is greek
showType() // The context's type is undefined
The context of the last showType() call
is the outermost scope
, window . Therefore, type is undefined, there is no window.type
call
and apply
are very similar—they invoke a function with a specified this
context, and optional arguments. The only difference between call
and apply
is that call
requires the arguments to be passed in one-by-one, and apply
takes the arguments as an array.
One more example in which, you have to create an object, and create a function that references this but has no this context.
const book = {
title: 'The Lost Symbol ',
author: 'Dan Brown',
}
function summary() {
console.log(`${this.title} was written by ${this.author}.`)
}
summary()
Since summary and book have no connection, invoking summary by itself will only print undefined, as it's looking for those properties on the global object.