JavaScript Essentials and the DOM
Use modern JavaScript to make pages interactive and manipulate the DOM safely and efficiently.
Content
Objects and prototypes
Versions:
Watch & Learn
AI-discovered learning video
Sign in to watch the learning video for this topic.
JavaScript Objects and Prototypes — the friendly backyard of inheritance
"This is the moment where the prototype chain finally clicks."
You're coming from Variables & Scoping and Functions & Closures, so you already know how scope decides who sees what and how functions can capture variables like a clingy koala. Objects and prototypes are the next stop: how JavaScript models data, behavior, and inheritance — but with its own unique, slightly rebellious flavor compared to class-based languages.
Why this matters (and where you'll see it)
- Web apps depend on objects everywhere: DOM nodes, config objects, user profiles, API responses. Knowing how objects and prototypes work saves you from bugs that feel like witchcraft.
- Prototypes are how JavaScript implements inheritance under the hood. When you call a method on obj, JS walks a chain (the prototype chain) to find it.
- Practical tie-in: when implementing client-side session helpers or user objects (recall State, Sessions, and Authentication), you'll often represent users and their behaviors as objects. Understanding prototypes helps you share methods efficiently and avoid leaking secrets.
What is an object in JavaScript? (Short answer)
- An object is an unordered collection of key-value pairs. Keys are strings (or symbols). Values can be anything, including functions.
- When a value is a function stored on an object, we call it a method.
Micro explanation: property lookup
When you read obj.foo, JavaScript checks:
- Does obj have an own property "foo"? If yes, return it.
- Otherwise, check obj's prototype (obj.proto), then that prototype's prototype, and so on until null.
That's the prototype chain in action.
Creating objects — the common patterns
// 1) Object literal (common)
const alice = { name: "Alice", greet() { console.log("hi") } };
// 2) Constructor + prototype
function Person(name){ this.name = name; }
Person.prototype.greet = function(){ console.log("Hi, I'm " + this.name); };
const bob = new Person("Bob");
// 3) ES6 class sugar (under the hood: prototypes)
class PersonClass {
constructor(name){ this.name = name; }
greet(){ console.log("Hi, I'm " + this.name); }
}
// 4) Object.create for explicit prototype
const proto = { speak(){ console.log(this.voice); } };
const dog = Object.create(proto);
dog.voice = "woof";
Quick table:
| Pattern | Best for | Prototype behavior |
|---|---|---|
| Object literal | simple objects | own properties, prototype = Object.prototype |
| Constructor function | many instances, pre-ES6 | methods on prototype shared by instances |
| class syntax | clarity + inheritance | same as constructor under the hood |
| Object.create | object with specific prototype | you pick the prototype explicitly |
Prototypes vs. Classes — the important mental model
- JavaScript is prototype-based. There are no classes under the hood;
classis syntactic sugar over prototype mechanics. - When you write
class A {}, JS creates a function A and sets methods on A.prototype.
Why care? Because mutations to a prototype affect all instances that inherit from it. That can be powerful — and dangerous.
Method lookup, shadowing, and property assignment
- Reading a property walks the prototype chain.
- Writing a property creates an own property (unless you explicitly set it on the prototype).
- If an own property exists, it shadows the prototype's property.
Example:
const p = { x: 1 };
const c = Object.create(p);
console.log(c.x); // 1 (from prototype)
c.x = 2; // creates own property on c
console.log(c.x); // 2 (own property now shadows prototype)
Use Object.hasOwnProperty or Object.prototype.hasOwnProperty.call(obj, "prop") to test ownership.
The prototype chain and instanceof
obj instanceof Cchecks whether C.prototype is in obj's prototype chain.Object.getPrototypeOf(obj)returns obj's immediate prototype. Avoid relying on__proto__in production code; use Object.getPrototypeOf instead.
this, functions, and prototypes — the trio
Remember how functions and closures control scope and how this is determined at call time? That matters here:
- When you call obj.method(),
thisinside method points to obj (unless arrow function — arrow functions don't get their ownthisand inherit it lexically). - Methods defined on the prototype still use
thisof the calling object; that's why prototypes let instances share behavior without losing per-instance data.
Example combining closures and object privacy (handy for tokens):
function Session(userId, token){
// token is private to this constructor via closure
let _token = token;
this.userId = userId;
this.getToken = function(){ return _token; };
}
Session.prototype.refresh = function(newToken){
// cannot access _token here; private closure belongs only to constructor-scoped functions
// so refresh that uses closure must be defined inside constructor as well
};
// Better: keep refresh inside constructor so it can update _token
function SecureSession(userId, token){
let _token = token;
this.userId = userId;
this.getToken = () => _token;
this.refresh = (newToken) => { _token = newToken; };
}
Note: methods placed on the instance in the constructor (to close over _token) are not shared across instances — they cost memory per instance. Use this pattern only when you actually need per-instance private data; otherwise prefer prototype methods.
Common pitfalls & security notes (especially relevant after learning auth flows)
- Do NOT store sensitive tokens in global objects or expose them via prototype methods unintentionally. Prototype methods can access
this, and ifthisis manipulated, you might leak data. - Avoid storing secrets in localStorage without considering XSS risk. Prefer short-lived memory storage and secure cookies for refresh tokens.
- Beware of prototype pollution: never
Object.assignuntrusted data directly into prototypes likeObject.prototype.
Quick checklist:
- Use prototype methods for shared behavior.
- Use closures inside constructors for per-instance private state if necessary.
- Don't mutate global prototypes (Object.prototype) in libraries.
When to use prototype inheritance vs composition
Favor composition in many web apps: give objects the behavior they need by composing small modules rather than building tall inheritance trees. In JS, prototypes are good for shared behavior (e.g., methods on many DOM wrapper instances), but composition (objects containing helper objects) often makes code simpler and safer.
Key takeaways
- Objects are collections of key-value pairs; prototypes implement inheritance via the prototype chain.
- Method lookup travels up the chain; writes create own properties that may shadow prototypes.
classis syntax sugar; prototypes are the real mechanism.- Use closures (from your Functions & Closures unit) for private instance state, but know the memory tradeoff.
- For session/auth objects: keep secrets out of shared prototypes, prefer instance closures or secure storage patterns.
Remember: prototypes are just JavaScript being JavaScript. Once you see the chain as a series of linked objects, rather than a mystery, you can bend it to your will (or at least stop accidentally shadowing properties and breaking login flows).
Happy prototyping — pun fully intended.
Comments (0)
Please sign in to leave a comment.
No comments yet. Be the first to comment!