Introduction
Tricky questions around this
are very common in Javascript interviews
Unfortunately, in one such interview, I was not able to solve them. So I decided to study more on the this
concept and document my understanding of it.
Most of the confusion around this
comes from not completely understanding what it exactly refers to. Before diving into how to know what this
refers to, let us understand why do we need this
Why this
?
this
helps functions to be reused with different objects.
Here is an example of it
function introduce() {
return this.name.toUpperCase();
}
function greet() {
var greeting = "Hello, I'm " + introduce.call( this );
console.log( greeting );
}
var person1 = {
name: "Person1"
};
var person2 = {
name: "Person2"
};
introduce.call( person1 ); // PERSON1
introduce.call( person2 ); // PERSON2
greet.call( person1 ); // Hello, I'm PERSON1
greet.call( person2 ); // Hello, I'm PERSON1
The above snippet needs a basic understanding of call and apply methods.
What it means is that the functions introduce() and greet() are being used with multiple objects (person1
and person2
).
function introduce(context) {
return this.name.toUpperCase();
}
function greet(context) {
var greeting = "Hello, I'm " + introduce(context);
console.log( greeting );
}
introduce(person1)
greet(person2)
Although we could have passed the objects to the functions like, like the example above,this
keyword provides a more elegant way of passing an object reference.
Misconceptions about this
One common misconception about this
is considering it to be referencing the function itself.
To understand that this
does not refers to the function itself. Let us understand it with an example.
function bark(i){
console.log("barking", i)
this.count ++;
}
bark.count = 0;
for(let i = 1 ; i < 5; i++){
bark(i);
}
// barking 1
// barking 2
// barking 3
// barking 4
console.log(bark.count)
// 0
Although, the function bark()
is called 4 times, the bark.count
value still remains 0
. This clearly indicates that the this
inside bark()
does not refer to the function itself.
When the code executes the bark.count
, it adds a count
property to the bark()
function, but this.count
inside does not refer to the same count function. Though the names are same, but the root objects are different.
this.count
is actually creating a new variable count
in the window object.
To keep in mind, what this
refers to is determined during the runtime. It is not based on how the function was declared but where the function is called. This is known as the call-site
What is a call-site?
The place where a function is called (not where it is declared).Yeah! its that's simple.
function a(){
console.log("Hey");
}
a() //< --- call site for a
But to determine what this
refers to (which is done during runtime), we need to keep in mind 4 rules.
Rules
1. Default Binding
Identifying a default binding is simple, if the function call just looks like the generic function call, its is default binding
function myFunction(){
console.log(this.a)
}
var a = 2;
myFunction() // 2
Inside myFunction()
, this.a
resolves to the global variable a
, as variables declared in the global scope (var a
) act as properties of the global scope ,so this.a === a
. Therefore, myFunction()
logs 2 in the console.
2. Implicit Binding
When a function call is preceded by a context object(also known as owing or containing object), this is when implicit binding comes into play.
E.g. :
function myFunction(){
console.log(this.a)
}
var obj = {
a : 2,
myFunction : myFunction
}
obj.myFunction(); // 2 <-- implicit binding
myFunction(); // undefined <-- default binding
Looking at the obj
it might seem that myFunction()
is owned by the obj
but remember, to determine what this
refers to, we need to see the call site. Here the call site is obj.myFunction()
.
Therefore, obj
owns myFunction
not because, myFunction
is inside obj
but because, in the call site, myFunction()
is preceded by obj
.
Problems with implicit binding
An issue with implicit binding is that it sometimes loses it implicit binding
and falls back to default binding
Consider the following example
function myFunction(){
console.log(this.a)
}
var obj = {
a : 2,
myFunction : myFunction
}
var myFunc = obj.myFunction; (*)
var a = 3;
myFunc(); // <-- 3 . How??
Although, obj.myFunction
(*) seems like implicit binding, its just a reference to myFunction()
itself. Moreover, the call site here is myFunc()
and this is just default binding, therefore, this.a
inside myFunction()
resolves to the global var a = 3
Another subtle way, implicit binding falls back to default binding is when we try to pass functions as a callback.
Consider this example
function myFunction(){
console.log(this.a)
}
var obj = {
a : 2,
myFunction : myFunction
}
function callFunctionAgain(callbackFn){
callbackFn();
}
var a = 3;
callFunctionAgain(obj.myFunction) // 3
This also results in 3 as the previous example. Test your understanding by taking help of the previous example on why it results to 3.
3. Explicit Binding
In implicit binding, we saw that to use a function we had to make a property reference to the function
function myFunction(){
console.log(this.a)
}
var obj = {
a : 2,
myFunction : myFunction // <-- property myFunction(left of ':') refering to
// function myFunction
}
But what if we wanted to use a function without changing the obj and force a function call on that object i.e. we do not want to create a property to reference the function as shown in the example above.
In such cases, we need the help of call() and apply().
Example :
function myFunction(){
console.log(this.a)
}
var obj = {
a : 2
}
myFunction.call(obj) //<-- 2
call() and apply() work the same with the only difference in the way the additional parameters are passed on to them.
While this blog is not intended to explain the use of call, apply (and bind). I would recommend googling about it if you aren't familiar with their concepts.
But using call/apply still does not solves the issue of binding falling back to default binding as mentioned in the previous section.
However, using bind
solves this issue. Bind returns a new function with this
already set to the context you specified
function myFunction(){
console.log(this.a)
}
var obj = {
a : 2
}
var a = 10
var myFunc = myFunction.bind(obj)
myFunc() // 2
4. new
Binding
In JavaScript, when a function is called with a new
keyword, it creates a new object with the this
referring to the newly created object.
It has not relation with the traditional class-oriented behavior.
Pretty much all functions can be called with a new keyword.
For our current discussion, when we use new
with a function, the following things happen
- a new object is created
this
refers to the newly created object- this new object is prototype linked Read More)
- if the function does not return its own object, the new invoked function call will return the newly created object.
Example :
function myFunction(a) {
this.a = a;
}
var myFunc = new myFunction( 2 );
console.log( myFunc.a )
Order of Precedence of the rules.
The order of precedence are in the following manner
new > explicit > implicit > default
this
for arrow functions
In case of arrow functions, this
refers to the arrow function's lexical scope
Example:
function myFunction() {
return (a) => {
console.log( this.a );
};
}
var obj1 = {
a: 2
};
var obj2 = {
a: 3
};
var myFunc= myFunction.call( obj1 );
myFunc.call( obj2 ); // 2, not 3
The this
of myFunction()
is set to obj1
. The lexical binding of arrow functions cannot be over-ridden even with new
.
When myFunction.call( obj1 )
executes, this
gets set to obj1.
Therefore, calling myFunc
with obj2
still resolves to the a
in obj1
Conclusion
To sum up, to determine what this
refers to, we need to carefully look for the call site and determine which of the above rules is being applied in the following precedence.
- If the function is called with new,
this
will be the newly created object. - If the function is called with call, apply or bind use the specified object in the first parameter.
- If the function is called with an object e.g:
obj1.myFunc()
, this is the containing object. - For default binding,
this
resolves to the global object.
Hope this blog gave you a better understanding of how this
works. Do drop your feedback and share it with your friends if you find it useful.
I write blogs documenting the things I learn.