1. How this Is Determined
In most languages, this (or its equivalent) always refers to the
instance a method was defined on. In JavaScript, this is set fresh
every time a function is called, based entirely on how it
was called — not where it was written:
const user = {
name: "Priya",
greet() {
console.log(`Hi, I'm ${this.name}`);
},
};
user.greet(); // "Hi, I'm Priya" -- called AS a method of user, so this === user
const detachedGreet = user.greet;
detachedGreet(); // "Hi, I'm undefined" -- called plainly, this is no longer user
greet is the exact same function in both calls — what changed is
how it was invoked. Calling user.greet() sets this
to whatever comes immediately before the dot. Pulling the same function out into a
plain variable and calling it directly loses that connection entirely; in a browser
or Node module, this ends up undefined (in strict mode,
which ES modules and classes always are).
const user = {
name: "Priya",
greetLater() {
setTimeout(() => {
// an arrow function has no `this` of its own -- it uses this
// from wherever it was WRITTEN, i.e. inside greetLater
console.log(`Hi, I'm ${this.name}`);
}, 100);
},
};
user.greetLater(); // "Hi, I'm Priya", even inside the callback
An arrow function deliberately has no this of its own
— it looks up this lexically, exactly like a normal variable, from the
nearest enclosing regular function. That's precisely why arrow functions are the
standard choice for callbacks that need to keep using the outer this,
like the setTimeout callback above.
An arrow function assigned as a method (greet: () => {'{'}...{'}'}) would look up this from outside the object literal entirely — usually the module scope — never the object itself. Regular methods (greet() {'{'}...{'}'}) are what correctly bind this to the calling object.
2. call, apply & bind
These three methods, available on every function, let you control what
this is explicitly, instead of relying on the call site:
function greet(greeting) {
console.log(`${greeting}, ${this.name}`);
}
const user = { name: "Priya" };
greet.call(user, "Hello"); // "Hello, Priya" -- arguments listed individually
greet.apply(user, ["Hello"]); // "Hello, Priya" -- arguments passed as an array
const greetPriya = greet.bind(user); // doesn't call it -- returns a new function
greetPriya("Hi"); // "Hi, Priya"
greetPriya("Hello"); // "Hello, Priya" -- this stays locked to user every time
call and apply do the same thing — invoke the function
immediately with a specified this — and differ only in how the
remaining arguments are passed (individually vs. as an array).
bind is different: it doesn't call the function at all, it returns a
new function with this permanently locked to whatever
was passed in, useful for passing a method somewhere else (like an event handler)
without losing its intended this.
3. Object Literals & Property Shorthand
Modern JavaScript has several shortcuts for writing objects that are worth knowing cold, because they show up in essentially every real codebase:
const name = "Priya";
const age = 29;
// Old way: repeating the key and the variable name
const userOld = { name: name, age: age };
// Property shorthand: when the key and variable name match, write it once
const user = { name, age };
// Method shorthand: no `function` keyword needed
const userWithMethod = {
name,
greet() {
return `Hi, I'm ${this.name}`;
},
};
// Computed property names: use a variable/expression AS a key
const field = "email";
const dynamicUser = { [field]: "priya@example.com" };
console.log(dynamicUser.email); // "priya@example.com"
Property shorthand and method shorthand are almost always used by default in modern code — the longer forms exist mainly so you can recognize them in older code. Computed property names matter whenever the key itself needs to come from a variable rather than being a fixed, known string.
4. Prototypes & the Prototype Chain
Every object in JavaScript has a hidden internal link to another object — its prototype — and property lookups that don't find a match on the object itself keep searching up that chain:
const animal = {
eats: true,
describe() {
return `This animal eats: ${this.eats}`;
},
};
const rabbit = Object.create(animal); // rabbit's prototype is animal
rabbit.jumps = true;
console.log(rabbit.jumps); // true -- rabbit's own property
console.log(rabbit.eats); // true -- not on rabbit, found on its prototype (animal)
console.log(rabbit.describe()); // "This animal eats: true" -- describe() is inherited too
rabbit doesn't have an eats property of its own — when you
access rabbit.eats, JavaScript doesn't find it on rabbit,
so it checks rabbit's prototype (animal), finds it there,
and returns it. This is prototypal inheritance: objects inherit
directly from other objects, rather than from a fixed template class the
way inheritance works in most other object-oriented languages.
You'll rarely write Object.create directly in everyday code — the class keyword in the next section is syntax sugar over this same prototype mechanism. Understanding the chain first is what makes class and extends click instead of feeling like a black box borrowed from another language.
5. Class Syntax
class gives object-oriented code a familiar, readable shape — under the
hood, it's still prototypes, just with far less boilerplate:
class Animal {
constructor(name) {
this.name = name; // runs once, when a new instance is created
}
describe() {
return `${this.name} makes a sound.`;
}
}
const dog = new Animal("Rex");
console.log(dog.describe()); // "Rex makes a sound."
class Dog extends Animal {
constructor(name, breed) {
super(name); // must call the parent's constructor before using `this`
this.breed = breed;
}
describe() {
// reuse the parent's version, then add to it
return `${super.describe()} It's a ${this.breed}.`;
}
}
const rex = new Dog("Rex", "Labrador");
console.log(rex.describe()); // "Rex makes a sound. It's a Labrador."
extends makes Dog inherit from Animal —
concretely, it wires Dog.prototype's prototype to
Animal.prototype, the exact chain from the previous section, just built
automatically. super(...) calls the parent class's constructor and is
required before this can be used in a subclass
constructor; super.describe() calls the parent's version of a method
you're overriding, letting you extend behavior instead of replacing it outright.
6. Hands-on Exercise
Model a small library with classes
Practice constructors, methods, inheritance and this together.
Requirements:
- Write a
Bookclass with a constructor acceptingtitleandauthor, and a methoddescribe()returning a string like"1984 by George Orwell". - Write an
EBookclass thatextends Book, adds afileSizeMBproperty in its own constructor (callingsuper()first), and overridesdescribe()to append the file size usingsuper.describe(). - Create a
Libraryclass with a private-by-convention array of books, and methodsaddBook(book)andlistBooks()that returns each book'sdescribe()output as an array of strings. - Add at least one
Bookand oneEBookinstance to aLibrary, then log the result oflistBooks(). - Extract
library.addBookinto its own variable and call it directly (detached fromlibrary) — observe and note in a comment what breaks and why.
Step 5 is deliberately reproducing the detachedGreet problem from earlier in this lesson — the fix, if you want it to keep working, is const addBook = library.addBook.bind(library).
7. Knowledge Check
Four quick questions. Expand each to check your answer.
Q1
Given const fn = obj.method; fn();, why might this inside method not be obj anymore?
Given const fn = obj.method; fn();, why might this inside method not be obj anymore?
this is set by how a function is called, not by where it was defined. obj.method() sets this to obj because of the dot syntax at the call site; once method is copied into the plain variable fn and called as fn(), that connection is gone — this becomes undefined (in strict mode/modules) or the global object otherwise.
Q2
What's the difference between fn.call(obj, a, b) and fn.bind(obj)(a, b)?
What's the difference between fn.call(obj, a, b) and fn.bind(obj)(a, b)?
call invokes fn immediately with this set to obj. bind doesn't call anything — it returns a brand-new function with this permanently locked to obj, which can then be called (or passed elsewhere, like an event handler) any number of times later. The end result of both lines above is the same call, but bind is for reuse; call is for a single, immediate invocation.
Q3
Given rabbit = Object.create(animal), why does rabbit.eats work even though rabbit never had an eats property set on it directly?
Given rabbit = Object.create(animal), why does rabbit.eats work even though rabbit never had an eats property set on it directly?
Property lookups that don't find a match on the object itself continue searching up its prototype chain. rabbit's prototype is animal, which does have an eats property, so the lookup finds it there and returns it — the same mechanism a class/extends hierarchy uses underneath.
Q4
In a subclass constructor, why must super(...) be called before using this?
In a subclass constructor, why must super(...) be called before using this?
super(...) runs the parent class's constructor, which is what actually initializes this for the instance being created. JavaScript enforces the call order directly — accessing this in a subclass constructor before calling super() throws a ReferenceError, because this genuinely doesn't exist yet at that point.