CodingNic

Object-Oriented Programming

The this Keyword

Object-Oriented Programming 20 min read

The this Keyword

Objectives

By the end of this chapter, you should be able to:

  • Explain that this depends on how a function is called, not where it’s written
  • Predict the value of this in a method call, a plain function call, and an arrow function
  • Explain why a constructor call sets this to the new object being built
  • Use .call(), .apply(), and .bind() to set this explicitly

💡 Why this matters: Every lesson left in this module leans on this, constructors, methods, inheritance, all of it. this trips up more beginners than almost anything else in JavaScript, because its value isn’t fixed. Get comfortable with it now, before adding classes on top.

this Depends on How a Function Is Called

In most languages, a variable’s value depends on where it’s declared. this doesn’t work that way. Its value is decided at the moment a function is called, based on how you called it, not where the function was written.

That’s the one idea this whole lesson is really about. Everything below is just that idea applied to four different situations.

Method Calls: this Is the Object Before the Dot

Call a function as a property of an object, and this inside it refers to that object.

javascript
"use strict";

const priya = {
  name: "Priya",
  greet() {
    return `Hi, I'm ${this.name}`;
  }
};

console.log(priya.greet());
// Hi, I'm Priya

priya.greet() has priya right before the dot, so this is priya. That’s it: whatever object appears before the dot when you call a method is what this refers to inside it.

Plain Function Calls: this Is Undefined

Pull that same function out of the object and call it on its own, with nothing before the dot, and this loses its connection entirely.

javascript
"use strict";

const priya = {
  name: "Priya",
  greet() {
    return `Hi, I'm ${this.name}`;
  }
};

const greetFn = priya.greet;

try {
  console.log(greetFn());
} catch (error) {
  console.log(error.constructor.name + ": " + error.message);
}
// TypeError: Cannot read properties of undefined (reading 'name')

greetFn() is called with nothing before the dot, so in strict mode (which this course uses throughout, see Module 2), this is undefined. Reading this.name when this is undefined throws. This is exactly why priya.greet() worked but greetFn() didn’t: same function, different call, different this.

Arrow Functions Inherit this From Their Surroundings

Arrow functions don’t get their own this at all. Instead, they use whatever this was already in scope where the arrow function was written. This matters a lot inside a regular function that contains another function.

javascript
"use strict";

const team = {
  name: "Rockets",
  membersRegular: ["Erin", "Jordan"],
  listRegular: function () {
    this.membersRegular.forEach(function (member) {
      console.log(this);
    });
  },
  membersArrow: ["Erin", "Jordan"],
  listArrow: function () {
    this.membersArrow.forEach((member) => {
      console.log(`${this.name}: ${member}`);
    });
  }
};

team.listRegular();
// undefined
// undefined

team.listArrow();
// Rockets: Erin
// Rockets: Jordan

listRegular calls .forEach() with a regular function (member) {...} callback. .forEach() calls that callback as a plain function, the same situation as the last section, so this inside it is undefined, not team. listArrow uses an arrow function instead, which has no this of its own, so it uses listArrow’s this, which is team. That’s why only the arrow version can reach this.name.

This is the practical reason to reach for an arrow function inside a method: when you want the inner function to keep using the surrounding this, not lose it.

Constructor Calls: this Is the New Object

Call a function with new, and JavaScript creates a brand new object, then runs the function with this set to that new object.

javascript
"use strict";

function Dog(name) {
  this.name = name;
}

Dog.prototype.bark = function () {
  return `${this.name} says woof!`;
};

const rex = new Dog("Rex");
console.log(rex.bark());
// Rex says woof!

Inside Dog, this.name = name assigns to the new object new just created, which is why rex.name ends up set to "Rex". You’ll see this exact behavior again in the next lesson, class constructors work the same way.

Setting this Explicitly: call, apply, and bind

Sometimes you want to choose this yourself, instead of relying on how a function happens to be called. .call(), .apply(), and .bind() all let you do that.

javascript
"use strict";

function introduce(greeting) {
  return `${greeting}, I'm ${this.name}`;
}

const maya = { name: "Maya" };

console.log(introduce.call(maya, "Hi"));
// Hi, I'm Maya

console.log(introduce.apply(maya, ["Hello"]));
// Hello, I'm Maya

const boundIntroduce = introduce.bind(maya);
console.log(boundIntroduce("Hey"));
// Hey, I'm Maya

.call(thisValue, arg1, arg2, ...) and .apply(thisValue, [arg1, arg2]) both run the function immediately with this set to whatever you pass in, they only differ in how you pass the remaining arguments (individually for .call(), as an array for .apply()). .bind(thisValue) is different: it doesn’t run the function, it returns a new function with this permanently locked to thisValue, which you can then call later, as many times as you want.

Try It

  1. Create an object with a name property and a method that returns a greeting using this.name. Call the method normally, then pull the method out into its own variable and call it plainly. Explain in your own words why the second call fails.
  2. Rewrite the listRegular example yourself: an object with an array property and a method that loops over it with a regular function callback, logging this each time. Confirm you get undefined, then fix it by switching to an arrow function.
  3. Write a plain function that uses this.value, and an object { value: 42 }. Call the function three different ways: with .call(), with .apply(), and by creating a bound version with .bind() and then calling that.

Recap

  • this is decided by how a function is called, not where it’s written.
  • In a method call (obj.method()), this is the object before the dot.
  • In a plain function call, this is undefined in strict mode.
  • Arrow functions don’t have their own this, they use whatever this was already in scope around them.
  • A constructor call with new sets this to the newly created object.
  • .call() and .apply() run a function immediately with a chosen this. .bind() returns a new function with this locked in for later.

Next lesson: objects, prototypes, and where classes fit in.