Kodokon kodokon.com

this, prototypes and classes

Understand how this is resolved dynamically, how the prototype chain works, and what classes actually add to the object model.

10 min · 3 questions

Open this lesson in Kodokon

The value of this is decided at the call site, not at declaration time. Four rules, in decreasing priority: calling with new, explicit binding (call, apply, bind), calling as a method (obj.fn()), and a plain call (fn()), which yields undefined in strict mode. The most common real-world bug follows directly: extracting a method from its object breaks the implicit binding.

JAVASCRIPT
const user = {
  name: "Ada",
  greet() {
    return "Hello, " + this.name;
  },
};
console.log(user.greet()); // "Hello, Ada"
const detached = user.greet;
// detached(); // TypeError: this is undefined
const bound = user.greet.bind(user);
console.log(bound()); // "Hello, Ada"
The implicit binding vanishes as soon as the method is extracted.

Arrow functions have no this of their own: they capture it lexically from the enclosing scope, just like a closure. That makes them ideal for callbacks, but counterproductive as methods on object literals, where this will point to the outer scope. Under the hood, every object delegates through its prototype chain: a missing property is looked up link by link until null is reached.

JAVASCRIPT
function Animal(name) {
  this.name = name;
}
Animal.prototype.describe = function () {
  return this.name + " is an animal";
};
const dog = new Animal("Rex");
console.log(dog.describe()); // "Rex is an animal"
console.log(Object.getPrototypeOf(dog) ===
  Animal.prototype); // true
console.log(dog.hasOwnProperty("describe")); // false
describe lives on the prototype, shared by every instance.

The class syntax does not change this model: it makes it readable. Methods declared in the class body land on Constructor.prototype and are therefore shared. Instance fields (including arrow functions assigned as fields) are recreated per instance: guaranteed this binding, but multiplied memory cost. Finally, #private fields provide privacy enforced by the engine, unlike the _underscore convention.

JAVASCRIPT
class BankAccount {
  #balance = 0;
  deposit(amount) {
    if (amount <= 0) throw new Error("Invalid amount");
    this.#balance += amount;
    return this;
  }
  get balance() {
    return this.#balance;
  }
}
const account = new BankAccount();
account.deposit(50).deposit(25);
console.log(account.balance); // 75
Engine-enforced private field and a chainable API.

Knowledge check

Make sure you remember the key points of this lesson.

  1. In strict mode, what is this during a plain call fn() with no object and no explicit binding?
    • globalThis
    • undefined
    • The object in which fn was declared
    • null
  2. Where are the methods declared in a class body (excluding fields) stored?
    • On each instance, copied at construction time
    • On Constructor.prototype, shared by all instances
    • In an inaccessible internal scope
  3. What is special about an arrow function with respect to this?
    • It binds this to the calling object, like a method
    • It has no this of its own and captures the one from the enclosing scope
    • It always binds this to globalThis
    • It forbids any access to this