Mastering this
Context 🐉
Join Goku and friends as we explore the mysterious world of the this
keyword in JavaScript. We'll learn how context works, how to control it with call
, apply
, and bind
, the differences between arrow functions and regular functions, and tackle complex scenarios involving nested functions. Just like the intricate battles in Dragon Ball Z, understanding this
requires strategy and insight!
Understanding this
in JavaScript
In JavaScript, this
refers to the object that is executing the current function. The value of this
depends on how the function is called.
Imagine this
as the Dragon Radar 🛰️ that points to different Dragon Balls depending on who is using it.
The Basics of this
Goku's Introduction
Goku has an object representing himself:
const goku = {
firstName: "Goku",
lastName: "Son",
fullName: function () {
return `${this.lastName}, ${this.firstName}`;
},
};
When we call goku.fullName()
, what does this
refer to?
console.log(goku.fullName()); // Output?
Answer
this
refers to thegoku
object because the method is called ongoku
.- Output:
"Son, Goku"
this
Depends on the Caller
Vegeta's Introduction
Now, Vegeta wants to use Goku's fullName
method:
const vegeta = {
firstName: "Vegeta",
lastName: "Prince of Saiyans",
fullName: goku.fullName,
};
console.log(vegeta.fullName()); // Output?
What does this
refer to when vegeta.fullName()
is called?
Answer
this
refers to thevegeta
object because the method is called onvegeta
.- Output:
"Prince of Saiyans, Vegeta"
Key Point
this
gets its value from the calling context, not from where the function was defined.
Global Context
Assigning Function to a Variable
If we assign fullName
to a standalone function and call it:
const fullName = goku.fullName;
console.log(fullName()); // Output?
What does this
refer to here?
Answer
- In non-strict mode,
this
refers to the global object (window
in browsers,global
in Node.js). - Since
window.firstName
andwindow.lastName
areundefined
, the output is"undefined, undefined"
.
Try It Out
Assign global variables and see the effect:
window.firstName = "Earthling";
window.lastName = "Human";
console.log(fullName()); // Output?
Answer
- Output:
"Human, Earthling"
Controlling this
with call
, apply
, and bind
Sometimes, we need to explicitly set the value of this
.
Using call
Frieza's Introduction
Frieza wants to introduce himself using Goku's fullName
method.
const frieza = {
firstName: "Frieza",
lastName: "Emperor",
};
function fullName() {
return `${this.lastName}, ${this.firstName}`;
}
console.log(fullName.call(frieza)); // Output?
Answer
call
invokes the function withthis
set tofrieza
.- Output:
"Emperor, Frieza"
Using apply
Similar to call
, but parameters are passed as an array.
Frieza's Introduction with Planet
function fullNameWithPlanet(planet) {
return `${this.lastName}, ${this.firstName} from ${planet}`;
}
console.log(fullNameWithPlanet.apply(frieza, ["Planet Vegeta"])); // Output?
Answer
apply
setsthis
tofrieza
and passes["Planet Vegeta"]
as arguments.- Output:
"Emperor, Frieza from Planet Vegeta"
Difference Between call
and apply
The only difference is how arguments are passed:
call(thisArg, arg1, arg2, ...)
apply(thisArg, [arg1, arg2, ...])
Using bind
bind
returns a new function with this
bound to the specified object.
Frieza's Bound Introduction
const boundFullName = fullName.bind(frieza);
console.log(boundFullName()); // Output?
Answer
boundFullName
is a new function withthis
set tofrieza
.- Output:
"Emperor, Frieza"
Try It Out
Use bind
with parameters:
const boundFullNameWithPlanet = fullNameWithPlanet.bind(frieza, "Planet Namek");
console.log(boundFullNameWithPlanet()); // Output?
Answer
- Output:
"Emperor, Frieza from Planet Namek"
Arrow Functions vs Regular Functions
Arrow functions handle this
differently.
Arrow Functions Do Not Have Their Own this
Gohan's Attempt with Arrow Function
Gohan tries to use an arrow function for fullName
:
const gohan = {
firstName: "Gohan",
lastName: "Son",
fullName: () => {
return `${this.lastName}, ${this.firstName}`;
},
};
console.log(gohan.fullName()); // Output?
What happens here?
Common Pitfall
- Arrow functions do not have their own
this
; they inheritthis
from the enclosing scope. - In this case,
this
refers to the global object, andfirstName
andlastName
areundefined
. - Output:
"undefined, undefined"
Regular Functions and this
Fixing Gohan's Introduction
Using a regular function fixes the issue:
const gohanFixed = {
firstName: "Gohan",
lastName: "Son",
fullName: function () {
return `${this.lastName}, ${this.firstName}`;
},
};
console.log(gohanFixed.fullName()); // Output?
Answer
- Output:
"Son, Gohan"
this
refers togohanFixed
because it's called as a method.
Forcing this
on Arrow Functions Doesn't Work
Even if we try to force this
using call
:
const fullNameArrow = () => {
return `${this.lastName}, ${this.firstName}`;
};
console.log(fullNameArrow.call(gohanFixed)); // Output?
Common Pitfall
- Cannot change
this
in arrow functions usingcall
,apply
, orbind
. - Output remains:
"undefined, undefined"
Nested Functions and this
Things get trickier with nested functions.
Nested Regular Functions Lose this
Piccolo's Special Move
Piccolo has an object with a method that contains a nested function.
const piccolo = {
firstName: "Piccolo",
lastName: "Junior",
specialMove: function () {
console.log(`${this.firstName} charges Special Beam Cannon!`);
function fire() {
console.log(`${this.firstName} fires Special Beam Cannon!`);
}
fire(); // What is `this` here?
},
};
piccolo.specialMove(); // Output?
What happens when fire()
is called?
Answer
- Inside
fire()
,this
refers to the global object. this.firstName
isundefined
.- Output:
Piccolo charges Special Beam Cannon! undefined fires Special Beam Cannon!
Fixing Nested this
with a Variable
One way to fix this is by storing this
in a variable.
const piccolo = {
firstName: "Piccolo",
lastName: "Junior",
specialMove: function () {
console.log(`${this.firstName} charges Special Beam Cannon!`);
const that = this; // Store `this` in `that`
function fire() {
console.log(`${that.firstName} fires Special Beam Cannon!`);
}
fire();
},
};
piccolo.specialMove(); // Output?
Answer
- Output:
Piccolo charges Special Beam Cannon! Piccolo fires Special Beam Cannon!
Using Arrow Functions to Preserve this
Another way is to use an arrow function for the nested function.
const piccolo = {
firstName: "Piccolo",
lastName: "Junior",
specialMove: function () {
console.log(`${this.firstName} charges Special Beam Cannon!`);
const fire = () => {
console.log(`${this.firstName} fires Special Beam Cannon!`);
};
fire();
},
};
piccolo.specialMove(); // Output?
Answer
- Arrow functions inherit
this
from the enclosing scope. - Output:
Piccolo charges Special Beam Cannon! Piccolo fires Special Beam Cannon!
Binding this
to the Nested Function
Alternatively, we can bind this
to the nested function.
const piccolo = {
firstName: "Piccolo",
lastName: "Junior",
specialMove: function () {
console.log(`${this.firstName} charges Special Beam Cannon!`);
function fire() {
console.log(`${this.firstName} fires Special Beam Cannon!`);
}
const boundFire = fire.bind(this);
boundFire();
},
};
piccolo.specialMove(); // Output?
Answer
- Output:
Piccolo charges Special Beam Cannon! Piccolo fires Special Beam Cannon!
Tasks and Practice
Task 1: Using call
and apply
Piccolo's Introduction
function introduce(planet, title) {
return `${this.lastName}, ${this.firstName}, the ${title} from ${planet}`;
}
const piccolo = {
firstName: "Piccolo",
lastName: "Junior",
};
- Use
call
to invokeintroduce
withpiccolo
,"Namek"
, and"Warrior"
. - Use
apply
to do the same.
Task
// Using call
console.log(/* Your code here */);
// Using apply
console.log(/* Your code here */);
Answer
// Using call
console.log(introduce.call(piccolo, "Namek", "Warrior"));
// Output: "Junior, Piccolo, the Warrior from Namek"
// Using apply
console.log(introduce.apply(piccolo, ["Namek", "Warrior"]));
// Output: "Junior, Piccolo, the Warrior from Namek"
Task 2: Using bind
Vegeta's Bound Introduction
const vegeta = {
firstName: "Vegeta",
lastName: "Prince of Saiyans",
};
const introduceVegeta = introduce.bind(vegeta, "Planet Vegeta", "Prince");
console.log(introduceVegeta()); // Output?
Answer
- Output:
"Prince of Saiyans, Vegeta, the Prince from Planet Vegeta"
Task 3: Arrow Function Context
Trunks' Attempt with Arrow Function
const trunks = {
firstName: "Trunks",
lastName: "Brief",
getFullName: () => {
return `${this.lastName}, ${this.firstName}`;
},
};
console.log(trunks.getFullName()); // Output?
What will be the output, and why?
Answer
- Output:
"undefined, undefined"
- Because
this
in an arrow function refers to the global object, nottrunks
.
Task 4: Fixing Arrow Function Issue
Modify trunks.getFullName
to correctly return his full name using an arrow function.
Task
- Hint: Use a regular function instead of an arrow function.
Answer
const trunksFixed = {
firstName: "Trunks",
lastName: "Brief",
getFullName: function () {
return `${this.lastName}, ${this.firstName}`;
},
};
console.log(trunksFixed.getFullName()); // Output: "Brief, Trunks"
Final Task: Understanding Complex this
Scenarios
Let's put your understanding to the test with a complex example.
The Scenario
Goku's Son Goten's Actions
const goten = {
name: "Goten",
crew: "Z Fighters",
actions: {
powerUp: function () {
console.log(`${this.name} powers up!`);
function attack() {
console.log(`${this.name} attacks with Kamehameha!`);
}
attack(); // What is `this` here?
},
fusionDance: function () {
console.log(`${this.name} performs the Fusion Dance!`);
const fuse = () => {
console.log(`${this.name} fuses with Trunks!`);
};
fuse();
},
crewShout: () => {
console.log(`Crew Shout: ${this.crew} assemble!`);
},
},
};
goten.actions.powerUp();
goten.actions.fusionDance();
goten.actions.crewShout();
Questions:
- What will be the output of
goten.actions.powerUp()
? - How can we fix the
attack()
function to correctly usethis.name
? - What will be the output of
goten.actions.fusionDance()
? - What will be the output of
goten.actions.crewShout()
and why?
Analyzing the Scenario
1. Output of goten.actions.powerUp()
this
inpowerUp
refers togoten.actions
.goten.actions
does not have aname
property.- Output:
undefined powers up! undefined attacks with Kamehameha!
2. Fixing attack()
Function
Option 1: Use a variable to store this
:
powerUp: function () {
const that = this;
console.log(`${that.name} powers up!`);
function attack() {
console.log(`${that.name} attacks with Kamehameha!`);
}
attack();
},
Option 2: Use an arrow function:
powerUp: function () {
console.log(`${this.name} powers up!`);
const attack = () => {
console.log(`${this.name} attacks with Kamehameha!`);
};
attack();
},
3. Output of goten.actions.fusionDance()
this
infusionDance
refers togoten.actions
.- Output:
undefined performs the Fusion Dance! undefined fuses with Trunks!
4. Output of goten.actions.crewShout()
crewShout
is an arrow function;this
refers to the global object.- Output:
Crew Shout: undefined assemble!
Final Task: Fixing the Code
Fix the code so that all outputs display the correct names and crew.
Updated Code:
const goten = {
name: "Goten",
crew: "Z Fighters",
actions: {
powerUp: function () {
console.log(`${goten.name} powers up!`);
const attack = () => {
console.log(`${goten.name} attacks with Kamehameha!`);
};
attack();
},
fusionDance: function () {
console.log(`${goten.name} performs the Fusion Dance!`);
const fuse = () => {
console.log(`${goten.name} fuses with Trunks!`);
};
fuse();
},
crewShout: function () {
console.log(`Crew Shout: ${goten.crew} assemble!`);
},
},
};
goten.actions.powerUp();
goten.actions.fusionDance();
goten.actions.crewShout();
Outputs:
Goten powers up!
Goten attacks with Kamehameha!
Goten performs the Fusion Dance!
Goten fuses with Trunks!
Crew Shout: Z Fighters assemble!
Common Pitfalls
Common Pitfalls
- Arrow Functions and
this
: Arrow functions do not have their ownthis
. They cannot be used as methods if you needthis
to refer to the object. - Losing Context in Nested Functions: Regular nested functions lose the
this
context. Use arrow functions or bindthis
. - Incorrect
this
in Methods: Ensure thatthis
refers to the correct object within methods.
Conclusion
Understanding this
is crucial for mastering JavaScript.
this
depends on how a function is called.- Use
call
,apply
, orbind
to controlthis
. - Arrow functions do not have their own
this
. - Be cautious with nested functions and their
this
context.
Happy coding, and may your JavaScript journey reach Super Saiyan levels! 💥