Simplifying "this" in JavaScript
Elaborating the JavaScript "this" with examples.
Overview
The this
keyword can be one of the most confusing concepts in JavaScript. We will explain this this
in layman's terms with enough examples.
this
is the object the function is a property of
One liner,
obj.someFunc(this);
Here, this
is in the function someFunc
and the someFunc
is a property of obj
. So this
represents the obj
.
Examples
Local Object
const myObject = {
name: 'foo',
task: function () {
return `This is ${this.name} task.`;
}
};
myObject.task();
This will return the text This is foo task.
.
By definition, here this
refers to the object myObject
. The function of task
is the property of myObject
.
Global Object
In the global scope, this
is the window
object.
In the global scope, if we define a function
function a() {
console.log(this);
}
It will print the window
object.
Since the a()
method is window.a()
, so according to our first definition, for a()
, this
is the window
object.
"this" is determined by Who Called the method?
this
is defined by the object called the method.
const a = function () {
console.log('For a, this is ', this);
const b = function () {
console.log('For b, this is ', this);
const c = {
hi: function () {
console.log('For c, this is ', this);
}
};
c.hi();
};
b();
};
a();
In this case, both a
and b
has the window
object as this
.
And for c
, since it is invoked by c
, then, the this
is the c
object.
Now, query might be, how b()
has the this
object window
, not the a
. Because b()
is not invoked by in similar way a.b()
. Instead, it is invoked like window.a(b())
"this" is not lexically scoped, it is dynamically scoped
const obj = {
name: 'Billy',
sing() {
console.log('a', this);
var anotherMethod = function () {
console.log('b', this);
};
anotherMethod();
}
};
obj.sing();
Here for a
, the this
context is obj
.
For b
, the this
context should be obj
. But since this
is not lexically scoped and follows method calls, b
has the this
context of window
.
This is why this
follows the dynamic scoped
instead of lexical scoped
.
Preserve this context
We can preserve this
context,
- By preserving it in a variable
- Using the arrow method
- Using bind method (Ex. first bind and then obj.prop()())
To solve this dynamic scope
issue, we can use the arrow method
. Arrow method
is bound to the lexical scoped
.
Using the Arrow method:
const obj = {
name: 'Billy',
sing() {
console.log('a', this);
var anotherMethod = () => {
console.log('b', this);
};
anotherMethod();
}
};
obj.sing();
Here both a
and b
have the this
context of obj
.
Using bind:
Another was to make the this
to lexical scoped
using bind()
method.
const obj = {
name: 'Billy',
sing() {
console.log('a', this);
var anotherMethod = function () {
console.log('b', this);
};
return anotherMethod.bind(this);
}
};
obj.sing()();
Here a
and b
has the this
context of obj
.
Preserving in lexical scope:
Holding the this
in another object can be used to preserve the this
context.
const obj = {
name: 'Billy',
sing() {
console.log('a', this);
var self = this;
var anotherMethod = function () {
console.log('b', self);
};
anotherMethod();
}
};
obj.sing();
Here both a
and b
have the this
context of obj
.
Manipulating "this"
We can manipulate/change the this
keyword using the following methods,
- call
- apply
- bind
Using call method:
We can use use the call
method to invoke a method, like the following,
var myMethod = function () {};
// following both statements are similar
myMethod();
myMethod.call();
We will use the same mechanism to inject the this
,
const wizard = {
name: 'Wizard',
health: 50,
heal() {
this.health = 100;
}
};
const archer = {
name: 'Archer',
health: 30
};
wizard.heal.call(archer);
console.log(archer);
This will print,
{
name: 'Archer',
health: 100
}
Here the health property is the same as the wizard
object.
Now, let's see another example of passing parameters using the call
method.
const wizard = {
name: 'Wizard',
health: 50,
heal(param1, param2) {
this.health = this.health + param1 + param2;
}
};
const archer = {
name: 'Archer',
health: 30
};
wizard.heal.call(archer, 10, 20);
console.log(archer);
This will add the params 10
and 20
with it existing value 30
.
So the printed value is,
{
name: 'Archer',
health: 60
}
Using apply
apply()
is similar to call()
, it uses call()
underlying. The only difference between call()
and apply()
is, in apply()
, the parameter is passed through the parenthesis.
var myMethod = function () {};
// following both statements are similar
myMethod();
myMethod.apply();
We can update/change the this
using the apply
, as follows,
const wizard = {
name: 'Wizard',
health: 50,
heal(param1, param2) {
this.health = this.health + param1 + param2;
}
};
const archer = {
name: 'Archer',
health: 30
};
wizard.heal.apply(archer, [10, 20]);
console.log(archer);
This will print the same value as the previous call()
method.
{
name: 'Archer',
health: 60
}
Using bind
Except for the call()
and bind()
, the bind()
does not invoke the method instantly. Instead, it returns a method that can be invoked later.
const wizard = {
name: 'Wizard',
health: 50,
heal(param1, param2) {
this.health = this.health + param1 + param2;
}
};
const archer = {
name: 'Archer',
health: 30
};
const archerHeal = wizard.heal.bind(archer, 10, 20);
archerHeal();
console.log(archer);
This will print the same value as the previous call()
or apply()
method.
{
name: 'Archer',
health: 60
}
We can make use of bind
and reach an interesting pattern. Let's we have a method that takes two numbers and returns the multiplied result.
const multiplyMethod = (num1, num2) => {
return num1 * num2;
};
Now using the function bind, we will create two methods from the previous method. Both methods will provide only one parameter.
- One will return multiply with 4
- Another will return multiplied with 10
let multiplyByTwo = multiplyMethod.bind(this, 4);
let multiplyByTen = multiplyMethod.bind(this, 10);
console.log(multiplyByTwo(2));
console.log(multiplyByTen(2));
This will return,
8
20
Benefits
Allow methods to use the properties of the object itself
const myObject = {
name: 'foo',
task: function () {
return `${this.name} task.`;
},
doTask: function () {
return `Do ${this.task()}`;
}
};
myObject.doTask();
This will return Do foo task.
Execute the same code for multiple objects
function showMyName() {
console.log(this.name);
}
const foo = {
name: 'foo',
showMyName
};
const bar = {
name: 'bar',
showMyName
};
foo.showMyName();
bar.showMyName();
This will return
foo
bar
Brain Teaser
Let's observe, a couple of examples
Example 01:
const myObj = {
name: 'myName',
myMethod() {
console.log(this);
}
};
myObj.myMethod();
Here the this
is the myObj
itself.
Example 02:
const myObj = {
name: 'myName',
myMethod() {
return function () {
return console.log(this);
};
}
};
myObj.myMethod()();
Since the return function is not called by the myObj
, here the this
object is the window
. It is using dynamic scope
instead of lexical scope
.
Example 03:
const myObj = {
name: 'myName',
myMethod() {
return () => {
return console.log(this);
};
}
};
myObj.myMethod()();
Since, the arrow method
strictly maintains the lexical scope
, here the this
represents the myObj
.