Prototypes, classes and modules
Two mechanisms decide how a JavaScript codebase is organised: how objects share behaviour (prototypes, with classes on top) and how files share code (modules). NestJS is built on classes and React Native on modules, so knowing what happens underneath turns confusing errors into quick fixes.
By the end you can
- Trace a property lookup along the prototype chain and say where a method really lives
- Write a class with a constructor, getters, private
#fields, static members andextends - Decide when composition beats inheritance
- Tell ES modules from CommonJS, and convert a CommonJS project to ESM, fixing each error Node prints
- Explain circular imports and tree-shaking well enough to spot them in a review
The everyday picture
Every Mangrove store has a copy of the company manual: opening hours, how to greet a customer, how to handle a return. The Riverside store also has a short page of local rules pinned by the till: "we open at 7, not 8, because of the morning commuter crowd". When a new staff member asks "what time do we open?", the manager checks the local page first. If the answer is there, that wins. If not, she looks it up in the company manual.
Nobody photocopies the whole manual into each store's local page. There is one manual, and when head office updates it, every store follows the new rule the next day. That is exactly how JavaScript objects share methods: the prototype is the manual, and the object's own properties are the local page.
The prototype chain
Every object has a hidden link to another object, its prototype. When you read obj.x, JavaScript first looks at obj's own properties. If x is not there, it follows the link and looks in the prototype, then the prototype's prototype, and so on. The first match wins. At the end of the chain is Object.prototype, whose prototype is null; if nothing matched by then, the result is undefined.
// proto.js: one manual, many stores
const franchiseManual = {
openingHour: 8,
greet() { return `Welcome to ${this.name}`; },
};
const riverside = Object.create(franchiseManual); // riverside's prototype is the manual
riverside.name = 'Riverside';
riverside.openingHour = 7; // a local rule shadows the manual
console.log(riverside.greet()); // found on the prototype, this = riverside
console.log(riverside.openingHour); // found on riverside itself
console.log(Object.hasOwn(riverside, 'greet'), 'greet' in riverside);
console.log(Object.getPrototypeOf(riverside) === franchiseManual);
console.log(Object.getPrototypeOf(Object.getPrototypeOf(franchiseManual))); // end of chain
console.log(riverside.closingHour); // not found anywhere
franchiseManual.closingHour = 23; // change the manual...
console.log(riverside.closingHour); // ...and every store sees itWelcome to Riverside
7
false true
true
null
undefined
23Three things to take from that output. A property on the object itself shadows the one on the prototype. A method found on the prototype still runs with this set to the object you called it on (Functions, scope and closures), which is why greet says "Riverside". And in checks the whole chain while Object.hasOwn checks only the object; that difference matters when you loop over or validate data.
Quick check
After the code above, you run delete riverside.openingHour. What is riverside.openingHour now?
delete removes only the own property. The next lookup does not find openingHour on riverside, follows the prototype link and finds the manual's 8. The local page was torn off, so the company rule applies again.
openingHour is found on riverside itself and shadows the manual's 8. greet is found one step up, on the manual. closingHour is not found anywhere, so the walk reaches null and gives undefined.