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
thisrefers to thegokuobject 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
thisrefers to thevegetaobject because the method is called onvegeta.- Output:
"Prince of Saiyans, Vegeta"
Key Point
thisgets 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,
thisrefers to the global object (windowin browsers,globalin Node.js). - Since
window.firstNameandwindow.lastNameareundefined, 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
callinvokes the function withthisset 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
applysetsthistofriezaand 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
boundFullNameis a new function withthisset 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 inheritthisfrom the enclosing scope. - In this case,
thisrefers to the global object, andfirstNameandlastNameareundefined. - 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" thisrefers togohanFixedbecause 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
thisin 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(),thisrefers to the global object. this.firstNameisundefined.- 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
thisfrom 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
callto invokeintroducewithpiccolo,"Namek", and"Warrior". - Use
applyto 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
thisin 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()
thisinpowerUprefers togoten.actions.goten.actionsdoes not have anameproperty.- 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()
thisinfusionDancerefers togoten.actions.- Output:
undefined performs the Fusion Dance! undefined fuses with Trunks!
4. Output of goten.actions.crewShout()
crewShoutis an arrow function;thisrefers 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 needthisto refer to the object. - Losing Context in Nested Functions: Regular nested functions lose the
thiscontext. Use arrow functions or bindthis. - Incorrect
thisin Methods: Ensure thatthisrefers to the correct object within methods.
Conclusion
Understanding this is crucial for mastering JavaScript.
thisdepends on how a function is called.- Use
call,apply, orbindto controlthis. - Arrow functions do not have their own
this. - Be cautious with nested functions and their
thiscontext.
Happy coding, and may your JavaScript journey reach Super Saiyan levels! 💥