Understanding 'this'
Mahesh Pratap
Posted on November 30, 2020
One of the most confusing mechanisms in JavaScript is this
keyword. Even a seasoned developer can get bewildered sometimes. So, let’s see if it's really that complicated!
this
is a special keyword that’s automatically defined in the scope of every function. But how JavaScript engines decide what value should it assign to this
.
Default Binding
In default binding, this
refers to the global object. Any plain undecorated function call will invoke default binding if the scope in which this
is used is not using strict mode
. Consider:
function foo() {
console.log(this.a);
}
var a = 2;
foo(); // 2
Variables declared in global scope as var a = 2
, function foo() {…}
are synonymous with global-object properties of the same name. They are not copies of each other, they are each other. In this snippet foo()
is called with a plain, undecorated function reference. Hence, the default binding applies here i.e., global object.
If strict mode
is in effect, the global object is not eligible for the default binding, so the this
is instead set to undefined
.
function foo() {
"use strict";
console.log(this.a);
}
var a = 2;
foo(); // TypeError: 'this' is 'undefined'
Although overall this
binding rules are entirely based on the call-site, the global object is only eligible for the default binding if the contents of foo()
are not running in strict mode
. The strict mode
state of the call-site of foo()
is irrelevant.
Implicit Binding
Implicit binding is invoked if the call-site has a context object. Now, what’s a context object!
Consider:
function foo() {
console.log(this.a);
}
var obj = {
a: 2,
foo: foo
};
obj.foo(); // 2
Here the call-site uses the obj
context to reference the function, so we can say obj
'owns' or 'contains' the function reference at the time function is called. Now consider this:
function foo() {
console.log(this.a);
}
var obj = {
a: 12,
foo: foo
};
var obj1 = {
a: 2,
obj: obj
};
obj1.obj.foo(); // 12
This may look confusing but only the last level of an object property reference chain matters to the call-site. So this
in foo()
refers to obj for the above function call.
Gotchas
Consider the following code:
function foo() {
console.log(this.a);
}
var obj = {
a: 2,
foo: foo
}
var bar = obj.foo;
var a = 'Oops, global';
bar(); // 'Oops, global'
Even though bar
appears to be a reference to obj.foo
, in fact, it’s really just another reference to foo
itself. Moreover, the call-site is what matters, and the call-site is bar()
, which is a plain, undecorated call, and thus the default binding applies.
There is one more way to lose implicit binding i.e. when we are using callbacks. You don’t know how your passed function is being called in the callback. Trust issues 😐️
Are default and implicit binding the same?
Consider the following code:
function foo() {
console.log(this.a);
}
var a = 2;
window.foo(); // 2
As discussed earlier variables declared in the global scope are synonymous with global-object properties of the same name. We can refer to these variables as window.a
, window.foo
respectively.
So whenever we are using plain, undecorated functions calls that means its context object is window and hence we get window
as this
in those cases.
Explicit Binding
Now let’s take the controls into our hands 😎️.
Functions have some utilities available to them (via their [[Prototype]]
), which can be useful for this task. Specifically bind(…)
, call(…)
, and apply(…)
methods.
call(), apply() & bind()
- We can specify what we want our
this
to be, using these functions. - One common thing about these functions is, it takes the first parameter - an object which is assigned as
this
on function invocation. -
call()
&apply()
invokes the function immediately whilebind()
just returns a new function.
function foo() {
console.log(this.a);
}
var obj = {
a: 2
};
foo.call(obj); // 2
foo.apply(obj); // 2
var bar = foo.bind(obj);
bar(); // 2
These are very useful functions in JavaScript and can’t be covered in this post so we’ll talk about these functions in-depth in another article.
new Binding
When a function is invoked with new
in front of it, the following things happen:
- A brand new object is created out of thin air.
- The newly constructed object is set as the
this
binding for that function call.
function foo(a) {
this.a = a;
}
var bar = new foo(2);
console.log(bar.a); // 2
The new foo(2)
will return {a:2}
in the above snippet.
There are few other things that happen too, but that is out of scope for this topic.
Conclusion
Once you know the call-site of a function, 4 rules can be applied to the call-site, in this order of precedence to determine this
binding:
- Called with
new
? Use the newly constructed object. - Called with
call()
,apply()
,bind()
? Use the specified object. - Called with context object owning the call? Use that context object.
- Default:
undefined
instrict mode
, global object otherwise.
For a more in-depth understanding of this
I highly recommend reading this & OBJECT PROTOTYPES of YOU DON’T KNOW JS series by Kyle Simpson.
Posted on November 30, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.