Skip to content

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:

javascript
const goku = {
  firstName: "Goku",
  lastName: "Son",
  fullName: function () {
    return `${this.lastName}, ${this.firstName}`;
  },
};

When we call goku.fullName(), what does this refer to?

javascript
console.log(goku.fullName()); // Output?
Answer
  • this refers to the goku object because the method is called on goku.
  • Output: "Son, Goku"

this Depends on the Caller

Vegeta's Introduction

Now, Vegeta wants to use Goku's fullName method:

javascript
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 the vegeta object because the method is called on vegeta.
  • 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:

javascript
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 and window.lastName are undefined, the output is "undefined, undefined".

Try It Out

Assign global variables and see the effect:

javascript
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.

javascript
const frieza = {
  firstName: "Frieza",
  lastName: "Emperor",
};

function fullName() {
  return `${this.lastName}, ${this.firstName}`;
}

console.log(fullName.call(frieza)); // Output?
Answer
  • call invokes the function with this set to frieza.
  • Output: "Emperor, Frieza"

Using apply

Similar to call, but parameters are passed as an array.

Frieza's Introduction with Planet

javascript
function fullNameWithPlanet(planet) {
  return `${this.lastName}, ${this.firstName} from ${planet}`;
}

console.log(fullNameWithPlanet.apply(frieza, ["Planet Vegeta"])); // Output?
Answer
  • apply sets this to frieza 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

javascript
const boundFullName = fullName.bind(frieza);

console.log(boundFullName()); // Output?
Answer
  • boundFullName is a new function with this set to frieza.
  • Output: "Emperor, Frieza"

Try It Out

Use bind with parameters:

javascript
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:

javascript
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 inherit this from the enclosing scope.
  • In this case, this refers to the global object, and firstName and lastName are undefined.
  • Output: "undefined, undefined"

Regular Functions and this

Fixing Gohan's Introduction

Using a regular function fixes the issue:

javascript
const gohanFixed = {
  firstName: "Gohan",
  lastName: "Son",
  fullName: function () {
    return `${this.lastName}, ${this.firstName}`;
  },
};

console.log(gohanFixed.fullName()); // Output?
Answer
  • Output: "Son, Gohan"
  • this refers to gohanFixed because it's called as a method.

Forcing this on Arrow Functions Doesn't Work

Even if we try to force this using call:

javascript
const fullNameArrow = () => {
  return `${this.lastName}, ${this.firstName}`;
};

console.log(fullNameArrow.call(gohanFixed)); // Output?

Common Pitfall

  • Cannot change this in arrow functions using call, apply, or bind.
  • 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.

javascript
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 is undefined.
  • 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.

javascript
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.

javascript
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.

javascript
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

javascript
function introduce(planet, title) {
  return `${this.lastName}, ${this.firstName}, the ${title} from ${planet}`;
}

const piccolo = {
  firstName: "Piccolo",
  lastName: "Junior",
};
  • Use call to invoke introduce with piccolo, "Namek", and "Warrior".
  • Use apply to do the same.

Task

javascript
// Using call
console.log(/* Your code here */);

// Using apply
console.log(/* Your code here */);
Answer
javascript
// 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

javascript
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

javascript
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, not trunks.

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
javascript
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

javascript
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:

  1. What will be the output of goten.actions.powerUp()?
  2. How can we fix the attack() function to correctly use this.name?
  3. What will be the output of goten.actions.fusionDance()?
  4. What will be the output of goten.actions.crewShout() and why?

Analyzing the Scenario

1. Output of goten.actions.powerUp()

  • this in powerUp refers to goten.actions.
  • goten.actions does not have a name property.
  • Output:
    undefined powers up!
    undefined attacks with Kamehameha!

2. Fixing attack() Function

Option 1: Use a variable to store this:

javascript
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:

javascript
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 in fusionDance refers to goten.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:

javascript
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 own this. They cannot be used as methods if you need this to refer to the object.
  • Losing Context in Nested Functions: Regular nested functions lose the this context. Use arrow functions or bind this.
  • Incorrect this in Methods: Ensure that this 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, or bind to control this.
  • 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! 💥


Additional Resources