35
JavaScript 'this' - Four rules to know
When you use this
in your code, it automatically resolves to an object or scope depending on the context at which is was defined.
But what are those possible contexts a this
call can refer to? Additionally, how can we use that information to find out which object a this
call will resolve to? Let's find out!
When used in a function, the this
keyword simply points to an object which it is bound to. In simple terms, it answers the question of where it should get some value or data from:
function alert() {
console.log(this.name + ' is calling');
}
In the function above, the this
keyword is simply referring to an object to which it is bound to access the "name" property from it.
But how do you know what object or scope the function is bound to? How do you make out what this
is referring to?
To unravel that, we need to understand the various binding rules that guide the this
behavior.
Generally, there are four kinds of bindings:
- Default Binding
- Implicit Binding
- Explicit Binding
- Constructor Call Binding
One of the first rules to remember is that if the function housing a this
reference is a standalone function, then that function is bound to the global object.
function alert() {
console.log(this.name + ' is calling');
}
const name = 'Kingsley';
alert(); // Kingsley is calling
name()
is a standalone, unattached function. As per the rule, it is bound to the global scope. Hence, the this.name reference resolves to the global variable const name = 'Kingsley'.
This rule, however, doesn't hold if name()
were to be defined in strict mode as so:
function alert() {
'use strict';
console.log(this.name + ' is calling');
}
const name = 'Kingsley';
alert(); // TypeError: `this` is `undefined`
Another scenario to look out for is whether the function is attached to an object (its context) at the call site.
According to the binding rule in JavaScript, a function can use an object as its context only if that object is bound to it at the call site. This form of binding is known as implicit binding.
Here is what I mean by that:
function alert() {
console.log(this.age + ' years old');
}
const myObj = {
age: 22,
alert: alert
}
myObj.alert() // 22 years old
Put simply, when you call a function using dot notation, this is implicitly bound to the object the function is being called from.
In this example, since alert is being called from myObj, the this keyword is bound to myObj. So when alert is called with myObj.alert(), this.age is 22, which is the age property of myObj.
Let's look at another example:
function alert() {
console.log(this.age + ' years old');
}
const myObj = {
age: 22,
alert: alert,
nestedObj: {
age: 26,
alert: alert
}
}
myObj.nestedObj.alert(); // 26 years old
Here, because alert
is ultimately being called from nestedObj, this
is implicitly bound to nestedObj
instead of myObj
.
An easy way to figure out which object this is implicitly bound to is to look at which object is to the left of the dot (.):
function alert() {
console.log(this.age + ' years old');
}
const myObj = {
age: 22,
alert: alert,
nestedObj: {
age: 26,
alert: alert
}
}
myObj.alert(); // `this` is bound to `myObj` -- 22 years old
myObj.nestedObj.alert(); // `this` is bound to `nestedObj` -- 26 years old
We saw that implicit binding had to do with having a reference in that object.
But what if we want to force a function to use an object as its context without putting a property function reference on the object?
We have two utility methods to achieve this: call()
and apply()
.
Along with a couple other set of utility functions, these two utilities are available to all functions in JavaScript via the [[Prototype]]
mechanism.
To explicitly bind a function call to a context, you simply have to invoke the call()
on that function and pass in the context object as parameter:
function alert() {
console.log(this.age + ' years old');
}
const myObj = {
age: 22
}
alert.call(myObj); // 22 years old
Now here's the fun part. Even if you were to pass around that function multiple times to new variables (currying), every invocation will use the same context because it has been locked (explicitly bound) to that object. This is called hard binding.
function alert() {
console.log(this.age);
}
const myObj = {
age: 22
};
const bar = function() {
alert.call(myObj);
};
bar(); // 22
setTimeout(bar, 100); // 22
// a hard-bound `bar` can no longer have its `this` context overridden
bar.call(window); // still 22
Hard binding is a perfect way to lock a context into a function call and truly make that function into a method.
The final and perhaps most interesting kind of binding is the new
binding which also accentuates the unusual behaviour of JavaScript in comparison to other class-based languages.
When a function is invoked with the new
keyword in front of it, otherwise known as a constructor call, the following things occur:
- A brand new object is created (or constructed)
- The newly constructed object is [[Prototype]]-linked to the function that constructed it
- The newly constructed object is set as the this binding for that function call.
Let's see this in code to get a better understanding:
function giveAge(age) {
this.age = age;
}
const bar = new giveAge(22);
console.log(bar.age); // 22
By calling giveAge(...) with new in front of it, we’ve constructed a new object and set that new object as the this for the call of foo(...). So new is the final way that you can bind a function call’s this .
To summarize, the this keyword, when used in a function, binds that function to a context object.
There are four kinds of bindings: default binding, implicit binding, explicit binding and constructor call binding (new).
Knowing these four rules will help you easily discern the context for a this
reference.
I hope you got something valuable from this article. If you are learning JavaScript, here are some other articles that might interest you:
Thank you for reading and see you soon.
P/S: If you are learning JavaScript, I created an eBook which teaches 50 topics in JavaScript with hand-drawn digital notes. Check it out here
35