this
Whenever we invoke any function in JavaScript, a brand new execution context is created at the run-time and an object is created which is referred to as this
.
this
refers to the current function execution context 1 and its value inside a function depends on the style of function invocation
.
The value of
this
depends on how the function is called and is evaluated during the run-time.
It is a very important and confusing concept. A lot of questions can be asked, which can be answered correctly only if you know the concept very well.
For example -
For the same method, value returned is different in both cases because value of this
depends on how the function/method is invoked
and not on where it is declared
1
2
3
4
5
6
7
8
9
10
11
12
var a = 2;
var obj = {
a: 33,
foo: function() {
return this.a;
}
}
console.log(obj.foo()); // 33
var bar = obj.foo;
console.log(bar()); // 2
We need to check that how the function is invoked, and based on that, we need to apply one of the four rules to find out the value of this
inside any function -
- Rule 1: Standalone function invocation - behaviour is different in
strict
ornon-strict
mode - Rule 2: Invoked as an object method
- Rule 3: Invoked using
call(), apply()
- Rule 4: Invoked with
new
keyword
With the introduction of ES6, the value of this
is now also dependent on the way a function is created -
- Function type -
arrow functions
ornormal functions
- Classes -
non-static method
orstatic method
Now, let us look at those rules in detail with the help of examples.
Rule 1: Standalone
function invocation
It is the most common way of invoking a function.
According to this rule, if the function gets invoked without any object reference or any other bindings. The value of this
will be equal to -
window
innon-strict
modeundefined
instrict
mode
non-strict
mode
Here, we have created a function foo
and, when we invoke that in the non-strict
mode, this
resolves to the global object
.
1
2
3
4
5
function foo() {
console.log(this); // [window]
}
foo();
Similarly, If declare a variable var a = 2;
, in the global scope, it is same as adding a variable a
inside a global scope. So, when we invoke foo
in this case it prints 2
which was assigned to var a
.
1
2
3
4
5
6
7
function foo() {
console.log(this.a); // 2
console.log(window.a); // 2, [because, this = window]
}
var a = 2;
foo();
In these examples, Rule 1
is applied because we are calling foo()
without any bindings, and also, nothing is present on the left side of the foo()
call.
strict
mode
If we try to execute the same code in strict
mode, we will always get this
as undefined
.
1
2
3
4
5
6
7
8
'use strict';
function foo() {
console.log(this); // undefined [because, this !== window]
console.log(window.a); // 2, [because, `a` is created on global window object]
}
var a = 2;
foo();
Rule 2: Invoked as an object method
If we invoke a function as a method
(function defined inside an object is known as method), then the value of this
inside a method
is always equal to the current object
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var myObj = {
a: 2,
// foo is declared as a method
foo: function() {
// returning the current value of `this`
return this;
}
}
// Way 1
console.log(myObj.foo()); // myObj{} -> {a: 2, foo: [f]}
console.log(myObj.foo() === myObj); // true
console.log(myObj.foo().a); // 2
// Way 2
// We can also store the object into a variable
// returned by invoking the method
var bar = myObj.foo();
console.log(bar); // myObj{} -> {a: 2, foo: [f]}
console.log(bar === myObj); // true
console.log(bar.a); // 2
This is how the invocation of an object method
works, but in interviews this question can be asked differently, as shown in the example below -
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Way 3
var myObj = {
a: 2,
// foo is declared as a method
foo: function() {
// returning the current value of `this`
return this;
}
}
var bar = myObj.foo;
// invoked as a standalone function(Rule 1)
console.log(bar()); // [window(non-strict mode), undefined(strict mode)]
console.log(bar === myObj); // false
console.log(bar.a); // undefined
Everything is same as that of the previous example, only on line 11, we are storing the reference of the function in a variable instead of invoking it.
Question 1: What will get printed in the console
(line 13, 14, 15)?
You can tell the interviewer that -
When we try to invoke that method at a later point(line 12), then instead of pointing to the object(obj
), it will be equal to global object
or undefined
.
Why?
Because, In case of object method
whenever, we store the reference of the function in a variable, the binding of the object is lost and function will be invoked as a standalone function (Rule 1) at a later stage.
Question 2: How can we fix this code?
After storing the reference to the method, we lost the binding to this
. Now to fix this binding issue we need to use bind()
-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var myObj = {
a: 2,
// foo is declared as a method
foo: function() {
// returning the current value of `this`
return this;
}
}
var bar = myObj.foo.bind(myObj);
// invoked as a standalone function(Rule 1)
console.log(bar()); // {a: 2, foo: [f]}
console.log(bar() === myObj); // true
console.log(bar().a); // 2
There is a simpler way to identify the current execution context 1 in case of Rule 1 and Rule 2.
You can check if there is something present on the left-hand side of the function invocation and -
- If yes, then
this
will be equal to thatobject
1
2
3
4
5
6
7
8
var obj = {
a: 2,
foo: function() {
console.log(this); // {a: 2, foo: [f]}, this === obj
}
}
obj.foo();
- Otherwise if nothing is present on the left-hand side then
this
will be equal to eitherglobal object
orundefined
1
2
3
4
5
function foo() {
console.log(this); // window
}
foo();
Rule 3: Invoked using call(), apply()
This is also known as Explicit binding.
By using call()
or apply()
we can change the value of execution context 1(this
) at the run time.
1
2
3
4
5
6
7
function foo() {
return this;
}
console.log(foo()); // this === window
console.log(foo.call({a: 2})); // this === {a: 2}
console.log(foo.apply({a: 4})); // this === {a: 4}
When we invoke the function foo()
- As a standalone function(line 5), it returned
this
which was equal to theglobal window object
- Using
call() or apply()
(line 6, 7) and passed a different object to it which acts as an execution context1 forfoo()
, it returned the same object{a: 2}
Rule 4: Invoked with new
keyword
If a function is invoked using a new
keyword(as a constructor function), then the value of this
inside that function is alway equal to an object.
1
2
3
4
5
6
7
8
9
10
// invoked as a constructor function
function Foo() {
this.a = 2;
this.b = 4;
console.log(this); // {a: 2, b:4}
console.log(window.a); // undefined
console.log(window.b); // undefined
}
var bar = new Foo();
If the same function is invoked as a normal function, then this
would be equal to window object
and all the properties (a and b
) will be created on window object
.
1
2
3
4
5
6
7
8
9
10
// invoked as a normal function
function Foo() {
this.a = 2;
this.b = 4;
console.log(this); // window
console.log(window.a); // 2
console.log(window.b); // 4
}
var bar = Foo();
ES6
Now, before we get into the interview questions. I would like to discuss the behaviour of this
on some of the new concepts introduced in ES6 and later versions.
this
in arrow functions
Arrow functions from ES6 do not have their own this
value instead they borrow this
from their lexical scope and use that value.
In the following example, bar()
is an arrow function(line 8) and invoked as a standalone function, but Rule 1 will not be applied here because Rule 1 can be applied only on normal function.
this
inside bar()
is equal to the obj
because bar()
is an arrow function and it borrows this
from the enclosing scope and in this case it borrows this
from the function foo()
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// example 1 - with arrow function
var obj = {
a: 2,
foo: function() {
console.log(this); // obj
// arrow function
bar = () => {
console.log(this); // obj
console.log(this.a); // 2
}
bar();
}
}
obj.foo();
If I try to write the same function as a normal functions, then you will observe the differences in terms of their behaviour.
Here, bar()
is a normal function and invoked as a standalone function. So Rule 1 will be applied here and accordingly this
inside bar
will point to the global window object
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// example 2 - with normal function
var obj = {
a: 2,
foo: function() {
console.log(this); // obj
// normal function
function bar() {
console.log(this); // window
console.log(this.a); // undefined
}
bar();
}
}
obj.foo();
Cheat sheet
There is a trick that can be used to answer most of the input-output questions in an interview. You just need to check two things -
- Is there any
explicit binding
is present on the function invocation -fn.call(), fn.apply(), fn.bind()
- Is there any
object
present on theleft side
of the function invocation -obj.method()
If none of them is present, than you need to look if the code is in the strict
mode or non-strict
mode.
- In non-strict mode -
this = window
- In strict mode -
this = undefined
You can refer to this flow chart for more clarity -