Chapter 3: Objects Delegate, They Don't Copy
Timothy arrived at the library with his laptop and a puzzled expression. He'd been reading about JavaScript inheritance, and something didn't add up.
"Margaret, I'm confused about something fundamental," he said, settling into his usual chair. "I learned that JavaScript uses prototypes for inheritance. But I've also learned that JavaScript has classes now. So which is it? Does JavaScript use prototypes or classes?"
Margaret smiled knowingly. "Both. And neither. It's more subtle than that."
"That's not helpful," Timothy said.
"It will be. First, let me ask you: what do you think inheritance means?"
Timothy thought for a moment. "When a child class inherits from a parent class, it gets all the parent's properties and methods."
"In languages like Python and Java, yes. The child class copies or includes the parent's behavior. But JavaScript doesn't work that way. JavaScript uses delegation."
"Delegation?"
"Instead of copying properties down, objects in JavaScript point to other objects and say: 'If I don't have what you're looking for, ask that object over there.' It's fundamentally different from inheritance."
Margaret pulled out her notebook. "Let me show you what I mean."
The Prototype Chain
Margaret wrote a simple example:
const parent = {
greet: function() {
console.log("Hello, " + this.name);
}
};
const child = Object.create(parent);
child.name = "Alice";
child.greet(); // "Hello, Alice" - works!
"Here," Margaret explained, "I created child using Object.create(parent). This creates a new object and sets up a hidden link—we call it [[Prototype]]—pointing to parent."
"Hidden?" Timothy asked.
"Yes, hidden. You can't see it directly, but it's there. When you call child.greet(), JavaScript does something clever: it looks for greet on child. Doesn't find it. Then it looks on child's prototype—which is parent. Finds it. Calls it."
Timothy leaned forward. "So child doesn't have its own greet method. It delegates to parent."
"Exactly. And here's the crucial part: greet is called with this = child. Even though the method lives on parent, this refers to the object that initiated the call."
She wrote another example:
const parent = {
greet: function() {
console.log("Hello, " + this.name);
}
};
const alice = Object.create(parent);
alice.name = "Alice";
const bob = Object.create(parent);
bob.name = "Bob";
alice.greet(); // "Hello, Alice"
bob.greet(); // "Hello, Bob"
"Both alice and bob delegate to the same parent object. But when greet runs, this is different. this is whoever called the method."
Timothy was following. "So they're not copying the method. They're sharing it."
"Precisely. This is delegation. It's elegant because it saves memory—you define a method once, and thousands of objects can use it. But each object can have its own this context."
Constructor Functions and .prototype
Timothy pulled up some of his own code. "But I've seen JavaScript code with constructor functions. They use .prototype. Is that the same as [[Prototype]]?"
Margaret nodded slowly. "Good question. This is where JavaScript gets confusing. There are two different things with similar names."
She drew a distinction:
[[Prototype]] (the hidden link):
const obj = Object.create(parent);
// obj.[[Prototype]] points to parent (invisible)
.prototype (the property on functions):
function User(name) {
this.name = name;
}
// User.prototype is a regular property on the User function
console.log(User.prototype); // { constructor: User }
"When you use the new keyword," Margaret continued, "something magical happens."
She wrote:
function User(name) {
this.name = name;
}
User.prototype.greet = function() {
console.log("Hello, " + this.name);
};
const alice = new User("Alice");
alice.greet(); // "Hello, Alice"
"When you call new User("Alice"), JavaScript:
- Creates a brand new object
- Sets that object's
[[Prototype]]toUser.prototype - Calls the
Userfunction withthisset to the new object - Returns the new object"
Timothy's eyes widened. "So User.prototype becomes the [[Prototype]] of the object?"
"Yes. new links them together. The .prototype property on the constructor function becomes the [[Prototype]] of the created object. It's a naming coincidence that causes endless confusion."
The Invisible vs. The Visible
Margaret sketched this out:
function User(name) {
this.name = name;
}
User.prototype.greet = function() {
console.log("Hello, " + this.name);
};
const alice = new User("Alice");
// What you can see:
console.log(alice.name); // "Alice" (own property)
console.log(User.prototype.greet); // the method (visible)
// What's invisible:
// alice.[[Prototype]] points to User.prototype
// How to verify the invisible link:
console.log(Object.getPrototypeOf(alice) === User.prototype); // true
"You can actually check that relationship," Margaret added. "Use Object.getPrototypeOf() to see what an object's [[Prototype]] points to. It's the modern, standard way to inspect the invisible link."
Timothy nodded. "So Object.getPrototypeOf(alice) returns the [[Prototype]]?"
"Exactly. It shows you the hidden link. There's also an older property called __proto__ that some code uses, but Object.getPrototypeOf() is the correct, standard approach. And if you ever need to change an object's prototype after creation, use Object.setPrototypeOf()—though in practice, you rarely need to."
"When you write alice.greet(), JavaScript walks this chain:
- Is
greetonalice? No. - Is
greetonalice's[[Prototype]](which isUser.prototype)? Yes! Call it."
"So the invisible [[Prototype]] is what actually makes delegation work," Timothy said.
"Correct. And .prototype on the constructor is just how we set up that invisible link. The constructor function doesn't actually inherit anything. It's a factory that creates objects with a pre-configured [[Prototype]]."
Timothy paused. "This is totally different from how Python works."
"Completely different. Python uses class inheritance—the child class actually becomes a subclass of the parent. JavaScript uses prototype delegation—objects point to other objects and ask for help when they need it."
Classes: Syntactic Sugar Over Prototypes
Timothy paused, then asked the natural follow-up. "Now that we understand how constructor functions set up prototypes, how do classes fit in?"
Margaret smiled. "Perfect question. Classes are syntactic sugar—a cleaner way to write the same pattern."
Timothy nodded. "What does syntactic sugar mean?"
"It means the syntax looks different, but the underlying mechanism is identical. Sugar makes it taste better—in this case, easier to read and write. The computer still does the same thing under the hood."
Timothy scrolled through his code. "But I've been using classes. Are classes different?"
Margaret shook her head. "No. Classes are syntactic sugar. They're a prettier way to write constructor functions and prototype manipulation. Under the hood, they still use prototypes."
She rewrote the constructor example as a class:
class User {
constructor(name) {
this.name = name;
}
greet() {
console.log("Hello, " + this.name);
}
}
const alice = new User("Alice");
alice.greet(); // "Hello, Alice"
"This does exactly the same thing as the constructor function. When you define a method in a class, it goes on the class's prototype. When you call new User(), it creates an object with [[Prototype]] pointing to User.prototype, just like before."
Timothy looked skeptical. "So classes are just... prettier constructor functions?"
"Functionally, yes. But syntactically, they're clearer about intent. You're saying 'this is a factory for creating objects,' not 'this is a function that happens to be called with new.'
There are also subtle semantic differences. For example, when you use extends and call super(), classes handle inheritance slightly differently than manual prototype chain manipulation. But the core mechanism—delegation through prototypes—is identical."
Margaret pulled up a comparison:
// Constructor function way
function User(name) {
this.name = name;
}
User.prototype.greet = function() {
console.log("Hello, " + this.name);
};
// Class way (does the same thing)
class User {
constructor(name) {
this.name = name;
}
greet() {
console.log("Hello, " + this.name);
}
}
// Both create the same prototype chain
const alice1 = new User("Alice"); // constructor function
const alice2 = new User("Alice"); // class
// alice1 and alice2 have identical structures
"The class syntax hides the .prototype manipulation. But it's still happening. You can even access it:"
class User {
greet() { console.log("Hello"); }
}
console.log(User.prototype.greet); // The method is still there
Why Delegation Instead of Copying?
Timothy leaned back. "But why does JavaScript use delegation instead of copying like other languages? It seems more complicated."
Margaret smiled. "It's actually more elegant once you understand it. Think about memory. In a language like Python, if you have 10,000 User objects, and you define a greet method on the User class, each object might store that method or reference it. But with delegation, the method lives in one place—User.prototype—and all 10,000 objects point to it."
"That's more efficient," Timothy said.
"Much more. And there's another benefit: if you modify the method on the prototype, all objects immediately see the change."
She wrote:
class User {
greet() {
console.log("Hello, " + this.name);
}
}
const alice = new User("Alice");
const bob = new User("Bob");
alice.greet(); // "Hello, Alice"
bob.greet(); // "Hello, Bob"
// Now change the method on the prototype
User.prototype.greet = function() {
console.log("Hi, " + this.name);
};
alice.greet(); // "Hi, Alice" - immediately changed!
bob.greet(); // "Hi, Bob" - immediately changed!
"Both objects immediately use the new method because they delegate to the prototype. You don't need to update each object individually."
Timothy nodded slowly. "So it's not just about memory. It's about dynamism."
"Exactly. JavaScript lets you change the prototype after objects are created, and all those objects adapt. That's powerful."
The Prototype Chain in Action
Margaret pulled up a more complex example:
class Animal {
speak() {
console.log(this.name + " makes a sound");
}
}
class Dog extends Animal {
bark() {
console.log(this.name + " barks");
}
}
const dog = new Dog("Rex");
dog.speak(); // "Rex makes a sound" - delegated to Animal
dog.bark(); // "Rex barks" - on Dog
// You can verify the prototype chain:
console.log(Object.getPrototypeOf(Dog.prototype) === Animal.prototype); // true
// The instanceof operator also checks the entire chain:
console.log(dog instanceof Dog); // true - dog is a Dog
console.log(dog instanceof Animal); // true - dog's prototype chain includes Animal
console.log(dog instanceof Object); // true - everything delegates to Object eventually
"That's clever," Timothy said. "The instanceof operator walks the prototype chain?"
"Exactly. It checks if the object's [[Prototype]] (or any prototype in the chain) matches the constructor's .prototype. So even though dog is a Dog, it's also an Animal because the chain connects them."
"When you call dog.speak(), JavaScript looks:
- Does
speakexist ondog? No. - Does
speakexist ondog's[[Prototype]](which isDog.prototype)? No. - Does
speakexist onDog.prototype's[[Prototype]](which isAnimal.prototype)? Yes! Call it."
Timothy leaned forward. "But here's the key: even though speak lives on Animal.prototype, when it runs, this.name still refers to dog, not to the Animal class."
"Exactly right," Margaret said. "This is the power of delegation. The method lives on Animal, but this refers to the object that called it. This is the same this behavior you learned before—remember Chapter 2?"
"Right! this is determined by how the function is called, not where it's defined."
"Perfect. So even in a multi-level prototype chain, this is always the original calling object. The delegation walks up the chain, but this stays fixed on dog."
"So it walks up the chain," Timothy said.
"Up the chain, or more accurately, along the delegation path. Each object points to its prototype, and if the method isn't found, it keeps looking."
Timothy frowned. "But be careful—this chain can get deep. If you have too many layers, lookups get slower."
"Right. In practice, keep the chain shallow. Most real code has just one or two levels."
A Warning: Own Properties vs. Prototype Properties
Timothy pointed at something in his code. "I have a question. What if an object has its own property with the same name as something on the prototype?"
Margaret leaned over. "Good question. Own properties shadow prototype properties."
class User {
constructor(name) {
this.name = name;
}
greet() {
console.log("Hello, " + this.name);
}
}
const alice = new User("Alice");
alice.greet(); // "Hello, Alice" - uses prototype method
// Override on the instance
alice.greet = function() {
console.log("Hi, " + this.name);
};
alice.greet(); // "Hi, Alice" - uses instance method
"When you set alice.greet, you're creating an own property on alice. Now when JavaScript looks for greet, it finds it on alice first and stops. It never reaches the prototype method."
"So the instance's own properties always win," Timothy said.
"Always. This is called shadowing. It's useful because it lets individual objects override behavior. But it can also be dangerous if you're not paying attention."
The Rule of Thumb
Margaret stood and walked to the window. "Timothy, here's what you need to understand about prototypes:
Objects delegate to their prototype. When you access a property, JavaScript looks on the object first, then on its prototype.
The
newkeyword sets up the delegation. It creates an object and links its[[Prototype]]to the constructor's.prototype.Classes use prototypes under the hood. They're just a cleaner syntax for the same mechanism.
The prototype chain is a lookup path. If a property isn't found on the object, JavaScript checks the prototype. If not there, it checks the prototype's prototype.
Own properties shadow prototype properties. If an object has its own property, it takes precedence over the prototype's property.
This is delegation, not inheritance. Objects ask their prototypes for help. They don't copy behavior."
Timothy wrote these down carefully.
"The key insight," Margaret continued, "is that JavaScript doesn't copy properties down like class-based languages do. It creates a chain of delegation. It's more dynamic, more memory-efficient, and once you understand it, actually more elegant."
She returned to her chair. "And now you've seen the three layers: variables determine where values live, this determines context, and prototypes determine how objects share behavior."
Timothy looked up. "There's more?"
Margaret laughed. "Oh, Timothy. We've barely scratched the surface. But you have solid foundations now. The next layers build on these."
She glanced at her watch. "But that's enough for today. Go practice with prototypes. Create objects, set up delegation, feel how it works. Your intuition will catch up to your understanding."
Timothy closed his laptop. "And after prototypes?"
"Closures. The event loop. Asynchronous code. Each one reveals another secret about how JavaScript works."
He stood to leave, then paused. "Margaret, thank you. A few weeks ago, JavaScript felt like chaos. Now it's starting to make sense."
"That's because you're learning to think like JavaScript, not like Python. Once you stop fighting the language and understand its philosophy, everything clicks."
Timothy smiled and headed out into the London evening.
Key Takeaways
Prototypes enable delegation, not inheritance — Objects point to other objects and delegate method calls, rather than copying behavior.
[[Prototype]]is the invisible link — Every object has a hidden[[Prototype]]property that points to another object (or null)..prototypeis a visible property on functions — Constructor functions have a.prototypeproperty that becomes the[[Prototype]]of created objects.The
newkeyword sets up the chain — When you callnew Constructor(), the created object's[[Prototype]]is set toConstructor.prototype.The prototype chain is a lookup path — When you access a property, JavaScript checks the object, then its prototype, then the prototype's prototype.
Own properties shadow prototype properties — If an object has its own property with the same name as a prototype property, the own property is used.
Classes are syntactic sugar for constructor functions and prototype delegation —
classsyntax creates the same prototype chains as constructor functions, just with clearer intent.Delegation is memory-efficient — All objects can share the same methods on the prototype without copying them.
Prototypes are dynamic — You can modify a prototype after objects are created, and all objects immediately see the changes.
Understand prototypes to understand JavaScript — Prototypes are fundamental to how JavaScript objects work. They enable the flexibility and dynamism that make JavaScript powerful.
Discussion Questions
Why do you think JavaScript chose delegation (prototypes) instead of inheritance (classes) as its core mechanism?
What's the difference between
object.[[Prototype]]andFunction.prototype, and why are the names so confusing?When would you use
Object.create()versus classes to create objects with a prototype chain?How does shadowing (overriding a prototype property with an own property) help or hurt code clarity?
If you modify a method on a prototype after objects are created, why do all objects immediately see the change?
Share your prototype explorations and questions in the comments!
About This Series
The Secret Life of JavaScript reveals how JavaScript actually works—not how you wish it worked. Through the conversations of Timothy (a curious developer learning his first new language) and Margaret (a wise JavaScript expert) in a Victorian library in London, we explore the ideas beneath JavaScript's quirky syntax.
Each article stands alone but builds toward deeper understanding. We've covered variables (let, const, var), context (this), and now prototypes (delegation). Whether you're new to JavaScript or just curious about why it works the way it does, this series illuminates the path.
Coming next: "The Secret Life of Closures" — where functions capture their surrounding scope, creating one of JavaScript's most powerful and misunderstood features.
Aaron Rose is a software engineer and technology writer at tech-reader.blog and the author of Think Like a Genius.
Top comments (1)
I wouldn't call classes syntactic sugar.
When people see a class structure their mind goes to object oriented programming patterns, because most people learned to work with classes in (almost) pure OO languages. And there the objects are static.
While a function in javascript is an
Objecttype, the object is dynamic. It is possible to swap the prototype withObject.setPrototypeOfafter the object is instantiated. Or change an object method as shown in the post.And that difference between static and dynamic means people are going to use static patterns that could be solved in a dynamic way.
While classes are technically syntactic sugar, it brought along a lot of bagage. And I'm not sure if it benefited the language.