Module 4 · Objects, Classes & Data Structures ⏱ 19 min

Object Methods & this

By the end of this lesson you will be able to:
  • Attach methods to objects with method shorthand and call them through the object
  • Explain how this is set by the call site, not the definition, and read it correctly
  • Diagnose and fix the lost-this trap when a method is detached or passed as a callback

Up to now your objects have been passive bundles of data — a name here, a score there — while the code that works on that data lived somewhere else, usually in a scattered pile of functions. That split holds for tiny scripts, but it breaks down fast: you end up passing the same object into a dozen functions, and nothing on the object tells you which functions were ever meant to touch it.

A method closes that gap. It is a function stored as a property, so the data and the behaviour that shapes it travel together. Instead of greet(user) you write user.greet() — the data knows what to do with itself. The one new idea a method adds is a special name, this, which lets the function refer to the very object it was called on.

flowchart LR
  D["data<br/>balance = 100"] --> O["object"]
  F["deposit(amount) method"] --> O
  O -->|"deposit(50)"| U["balance becomes 150"]
  style O fill:#e0900b,color:#fff
  style U fill:#1e7f3d,color:#fff
A method sits beside the data on the same object, and reaches that data through this.

Methods and this

You can put a function on an object the same way you assign any value, but modern JavaScript also offers tidy method shorthand, which you should reach for by default:

const account = {
  balance: 100,
  deposit(amount) {            // shorthand for deposit: function(amount)
    this.balance += amount;    // 'this' is the object the call happened on
    return this.balance;
  }
};

The word this is how a method refers to the object it was invoked on. When you write account.deposit(50), the thing before the dot — account — becomes this for the duration of the call, so this.balance means account.balance. Read the call aloud as 'account, run your deposit', and you have read this correctly.

Define methods, call them through the object, and watch this resolve to the receiver. Press Run.
const account = {
  owner: "Ada",
  balance: 100,
  describe() {
    return `${this.owner} has $${this.balance}`;
  },
  deposit(amount) {
    this.balance += amount;
    return this.balance;
  }
};

console.log(account.describe());
console.log(account.deposit(25));
console.log(account.describe());

this is decided by the call, not the definition

This is the single most important fact about this, and the one beginners get wrong: this is not a permanent property of the method, fixed when it was written. It is chosen fresh on every call, based entirely on how the function is invoked. The definition of deposit never says which object this will be — it only promises 'whatever object I am called on, update its balance'.

So a.deposit(5) sets this to a, and b.deposit(5) sets this to b, even though both calls run the exact same function body. There is no lasting bond between a method and the object where it happens to live. The object before the dot, at the moment of the call, is the whole story.

flowchart TD
  M["describe() method"] -->|"a.describe()"| A["this = a"]
  M -->|"b.describe()"| B["this = b"]
  style A fill:#1e7f3d,color:#fff
  style B fill:#3776ab,color:#fff
One shared method, two objects. this points at whichever object the dot selected.
One function shared by two objects. Each call picks its own this from the dot.
function describe() {
  return `${this.label}: ${this.value}`;
}

const a = { label: "A", value: 1, describe };
const b = { label: "B", value: 2, describe };

console.log(a.describe());
console.log(b.describe());

Arrow functions don't have their own this

There is a second kind of function in JavaScript that behaves very differently here. An arrow function does not get its own this at all — instead it inherits this from the surrounding code, wherever it was defined. Write const f = () => this.x inside a method and this still means the method's receiver, because the arrow reached outward and borrowed it.

That sounds exotic until you see why it exists. Callbacks and timers call your function as a bare f(), with no object before the dot — which, for a regular function, throws this away. An arrow ignores that call-site rule and keeps the this it captured, which is exactly what you want when a method hands a callback to something else. You will use this fact to repair a broken timer below.

flowchart TD
  R["obj.method() regular fn"] -->|"this = obj"| CR["from the call site"]
  AR["arrow inside method"] -->|"this = obj"| CA["borrowed from outer scope"]
  style CR fill:#e0900b,color:#fff
  style CA fill:#1e7f3d,color:#fff
Regular function: this comes from the call site. Arrow function: this comes from where it was written.
A detached method loses this; an arrow wrapper or .bind restores it.
const counter = {
  count: 0,
  bump() { this.count += 1; return this.count; }
};

// An arrow keeps `this` from the surrounding scope, so the inner call keeps its receiver.
const safe = () => counter.bump();
console.log(safe());
console.log(safe());

// .bind fastens the receiver on permanently, no matter how the function is later called.
const bound = counter.bump.bind(counter);
console.log(bound());
Exercise

What does this print? The object before the dot is p, so inside label that is this.

const p = {
  name: "Kit",
  label() { return `[${this.name}]`; }
};
console.log(p.label());
Exercise

Write makeWallet() that returns an object with balance starting at 0 and two methods: credit(amount) adds amount to the balance and returns it; spend(amount) subtracts amount and returns it. Use this inside the methods to read and update balance.

function makeWallet() {
  return {
    balance: 0,
    // add credit(amount) and spend(amount) using this
  };
}
Exercise

runLater grabs the tick method off counter, then calls the bare function — so tick runs with no receiver and this is lost. Keep makeCounter, but change the one line that detaches tick so it still runs with counter as its receiver (use .bind, or call it through counter directly).

function makeCounter() {
  return {
    count: 0,
    tick() { this.count += 1; }
  };
}

function runLater(counter) {
  const fn = counter.tick;   // detached from counter
  fn();                       // runs with no receiver, so this is lost
  return counter.count;
}
Exercise

Methods can call each other through this. Add total() to the cart object — it returns the sum of every item's price — and withTax(rate), which returns this.total() multiplied by (1 + rate). withTax MUST call this.total() rather than re-adding the prices; that is the skill being tested.

const cart = {
  items: [{ price: 10 }, { price: 5 }, { price: 20 }],
  // add total() and withTax(rate); withTax calls this.total()
};
Exercise

An object q has who() { return this; }. When you call q.who(), what sets the value of this inside the method?

Recap

  • A method is a function stored on an object; reach for method shorthand name(args) { ... }.
  • Inside a method, this is the object the call happened on — the receiver before the dot.
  • this is decided per call, by how the function is invoked, never fixed at definition. a.f() and b.f() run the same code with different this.
  • Arrow functions have no this of their own — they borrow it from the surrounding scope, which is why they fix callback bugs.
  • Detaching a method (storing it, passing it to a callback) loses this. Repair it with an arrow wrapper or .bind(receiver).

Next you'll turn these one-off objects into reusable blueprints with constructor functions and the prototype chain — the machinery this was built to serve.

Checkpoint quiz

Inside the call account.deposit(50), what is this?

Why does setTimeout(obj.method, 0) often fail to reach obj's data?

Go deeper — technical resources