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
}
}
// 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.
// 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
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.
Losing this
const timer = {
ticks: 0,
start() {
setTimeout(function () {
this.ticks++
}, 10)
},
}
timer.start()
await new Promise(r => setTimeout(r, 40))
console.log(timer.ticks)
Press Run to try your code, then Check answer to grade it.Check yourself
Inside an arrow function, what is this?
Tip: use the ← and → keys to move between lessons.