Step 1 of 5 · Reading · ~3 min
Learn
Iteration and Generators
The Prototype Chain
JavaScript Intermediate showed you that class is sugar over a chain of prototype objects and that instanceof walks it. This lesson opens the machinery: what new does step by step, the exact rule instanceof applies, why reads walk the chain but writes do not, and how that asymmetry causes shared-state and security bugs. By the end you can predict where any property lives.
What new does
new Counter(5) on a function constructor performs four steps:
- Create an empty object whose
[[Prototype]]isCounter.prototype. - Call
Counterwiththisset to that object and the arguments you passed. - If
Counterreturned an object, that object is the result of the whole expression. - Otherwise the object from step 1 is the result.
Step 3 is the one people miss. A return value that is a primitive is ignored:
A class constructor refuses step 2: calling it without new throws a TypeError, because the engine builds this itself. So only plain functions can be re-created by hand.
The rule behind instanceof
x instanceof C is true when the object C.prototype appears somewhere on x's chain. Start at Object.getPrototypeOf(x), move one link at a time, and stop at null. Two cases never enter the walk: if x is not an object (a number, a string, null) the answer is false straight away, and an object made with Object.create(null) has an empty chain, so it is an instance of nothing, not even Object.
Reads walk the chain, writes do not
A read of obj.key visits obj, then its prototype, and so on until some object owns key. An assignment to obj.key normally creates or updates an own property of obj and leaves the prototype alone:
Two exceptions come from property descriptors. An inherited setter runs instead of creating an own property, with this set to the object you assigned on. An inherited property with writable: false makes the assignment fail (a TypeError in strict mode, silently ignored otherwise).
Shared state on a prototype
Every instance sees the same prototype object, so a mutable value stored there is shared:
Put per-instance data in the constructor (this.items = []), and keep only functions and constants on the prototype.
Prototype pollution
__proto__ is an accessor inherited from Object.prototype. Code that copies keys with target[k] = value can therefore be tricked into writing to Object.prototype itself, and every object in the program then inherits the new property:
Defences: skip the keys __proto__, constructor and prototype when merging, build lookup tables with Object.create(null), or use a Map.
Common mistakes
- Putting an array or object literal on a prototype and expecting each instance to get its own copy.
- Using the loop
for (k in obj)to list own keys. It includes inherited enumerable ones; useObject.keysorObject.prototype.hasOwnProperty.call(obj, k). - Calling
obj.hasOwnProperty(k)on anObject.create(null)object. It has no such method and throws. - Assuming
x instanceof Objectis true for every value. Primitives and null-prototype objects answerfalse.
Your exercise
You will rebuild new, instanceof and an "who owns this property" lookup, then run them against a small Animal/Dog/Puppy hierarchy. The usual slip is missing step 3 above, or letting a primitive like 5 reach Object.getPrototypeOf, which quietly answers for it.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…