Lesson 16 of 18 · 0 of 1 exercises solved

"this", Prototypes & the Object Model

You will learn how work underneath classes and how JavaScript decides what "this" refers to. This explains many behaviours that otherwise look surprising.

// "this" is decided by HOW a function is called, not where it is defined.
const user = {
  name: 'Ada',
  greet() { return `Hi, ${this.name}` },
}

console.log(user.greet())        // "Hi, Ada"  — called as a method

const fn = user.greet
console.log(fn())                // "Hi, undefined" — lost its receiver

// Fix 1: bind
const bound = user.greet.bind(user)
console.log(bound())             // "Hi, Ada"

// Fix 2: call / apply set "this" for one invocation
console.log(user.greet.call({ name: 'Grace' }))   // "Hi, Grace"

// Fix 3: arrow functions have NO own "this" — they inherit it lexically
class Timer {
  constructor() { this.ticks = 0 }
  start() {
    setInterval(() => { this.ticks++ }, 1000)   // arrow keeps "this" = the Timer
  }
  startBroken() {
    setInterval(function () { this.ticks++ }, 1000)  // "this" is not the Timer
  }
}
 
JavaScript sandboxCtrl+Enter runs
// Every object has a hidden link to a prototype. Lookup walks the chain.
const animal = {
  speak() { return `${this.name} makes a sound` },
}

const dog = Object.create(animal)   // dog's prototype is animal
dog.name = 'Rex'

console.log(dog.speak())                          // "Rex makes a sound"
console.log(Object.getPrototypeOf(dog) === animal) // true
console.log(dog.hasOwnProperty('speak'))           // false — it is inherited

// class is exactly this, with nicer syntax
class Animal {
  constructor(name) { this.name = name }
  speak() { return `${this.name} makes a sound` }
}
class Dog extends Animal {
  speak() { return `${super.speak()} (a bark)` }
}

const rex = new Dog('Rex')
console.log(rex.speak())
console.log(Object.getPrototypeOf(Dog.prototype) === Animal.prototype)  // true
// Methods live on the PROTOTYPE, shared by every instance — not copied per object.
 
JavaScript sandboxCtrl+Enter runs
// Private fields (#) are real privacy, enforced by the engine
class BankAccount {
  #balance = 0                      // not accessible from outside, at all

  constructor(owner) { this.owner = owner }

  deposit(amount) {
    if (amount <= 0) throw new RangeError('amount must be positive')
    this.#balance += amount
    return this
  }

  get balance() { return this.#balance }      // getter — read like a property

  static from(owner, initial) {               // static factory
    return new BankAccount(owner).deposit(initial)
  }
}

const acct = BankAccount.from('Ada', 100)
console.log(acct.balance)     // 100
// acct.#balance            -> SyntaxError, cannot even be referenced

// Freezing and copying
const config = Object.freeze({ retries: 3 })
// config.retries = 5      -> silently ignored (throws in strict mode)

const clone = structuredClone({ a: { b: [1, 2] } })   // real deep copy
 
JavaScript sandboxCtrl+Enter runs

Common question

Does "this" refer to the function it's written in?

JavaScript objects fall back on other objects: when a is missing, the lookup follows the prototype chain. class syntax builds these chains and puts methods on the prototype so every shares them. this is the object a method was called on, worked out at each call, which is why a method pulled off its object and passed somewhere else forgets who it belonged to.

Keep this predictable: call methods on their objects, use arrow functions for that need the surrounding this, and bind when you have to pass a method as a value. Learn the prototype chain, and instanceof, inheritance and the odd behaviour of borrowed methods stop being mysteries.

Source: The ECMAScript specification sets this fresh on each call, based on how the call is written. The definition mentions no class and no place of writing.

Common mix-ups

Each of these sounds reasonable. Think about why it is wrong, then open the explanation.

  • this means the current function, or the object the function was written in.

  • A class copies its methods into every instance.

  • bind, call and apply are obscure and never needed.

Practice

The timer never counts because the callback's this is not the timer object. Fix it so that after the timeout fires, timer.ticks is 1. An arrow function or bind will do.

Exercise

Losing this

2 checks
const timer = {
  ticks: 0,
  start() {
    setTimeout(function () {
      this.ticks++
    }, 10)
  },
}

timer.start()
await new Promise(r => setTimeout(r, 40))
console.log(timer.ticks)
 
JavaScript sandboxCtrl+Enter runs
Output
Press Run to try your code, then Check answer to grade it.

Check yourself

Quick check

Inside an arrow function, what is this?

Finished reading?1 exercise still open. You can come back to it later.

Tip: use the ← and → keys to move between lessons.