javascript · memo
In one line: Every object has a hidden [[Prototype]] link; a read that misses own
properties walks the chain to null — objects delegate at run time, nothing is
copied. class is mostly syntax over constructor functions and their .prototype.
Download PDF Print view LaTeX source
How it works
- Read
o.k: own property? else[[Prototype]]’s, … elseundefined. A getter found up the chain runs withthis=o(the receiver). - Write
o.k = vcreates/updates an own property — shadowing the inherited one — unless the chain has a setter fork(it runs) or a non-writablek(write fails; throws in strict). Object.create(proto)links without a constructor;Object.create(null)= a dictionary with no inherited keys.setPrototypeOflater deoptimises.- Own:
Object.hasOwn,Object.keys(own enumerable strings) · chain:k in o,for…in.instanceof C= isC.prototypeon the chain — fails across realms (useArray.isArray). - Swift: a protocol is checked at compile time; a prototype is a live object — patch
Array.prototypeand every array changes now.
Classes — sugar, except…
class Counter extends Base {
#n = 0; // private: a brand, not a property
static { /* static init block: runs once */ }
inc = () => ++this.#n; // own field, one fn per instance
get value() { return this.#n; } // accessor on the prototype
static is(o) { return #n in o; } // brand check
}
- Not sugar: no call without
new; strict body; methods non-enumerable and not constructible; TDZ;super()beforethis;extends Array/Errormakes real exotic instances (ES5 couldn’t). #private: invisible to reflection;this.#nthrough a Proxy of the instance throws TypeError (not forwarded).supercomes from the method’s[[HomeObject]](fixed at definition), notthis: a copied method keeps it.
Descriptors and locking
Data {value, writable, enumerable, configurable} · accessor {get, set,
enumerable, configurable}. Literal/assignment: all true; defineProperty: omitted = false. configurable:false: no delete, no redefine (writable may still go true→false).
[2pt]
| add | delete | change value | sets on every own prop | |
|---|---|---|---|---|
preventExtensions | × | ✓ | ✓ | — |
seal | × | × | ✓ | configurable:false |
freeze | × | × | × | + writable:false |
[1pt] All shallow (nested objects stay mutable); setters still run; violations are silent in sloppy, throw in strict.
Proxy + Reflect
const logged = t => new Proxy(t, {
get: (t, k, r) => (log('get', k), Reflect.get(t, k, r)),
set: (t, k, v, r) => Reflect.set(t, k, v, r) }); // false: throws
r = receiver: getters see the proxy as this. 13 traps (has
deleteProperty ownKeys apply construct…); Reflect.x = trap x’s default.
Symbols, iterators, generators
Symbol()= unique key, skipped byObject.keys/for…in/JSON. Well-known hooks:Symbol.iterator(for-of, spread, destructuring),asyncIterator,toPrimitive.- Iterator:
next()→{value, done}, lazy; optionalreturn()runs onbreak(cleanup). function*: calling it runs nothing; eachnext(v)resumes to the nextyield, andvbecomes the value of the paused yield — a coroutine (redux-saga;co, pre-async/await).
function* g() { const a = yield 1;
const b = yield a + 1; return a + b; }
const it = g(); // nothing ran yet
it.next('x'); // {value: 1, done: false} first arg is ignored
it.next(10); // a = 10 -> {value: 11, done: false}
it.next(5); // b = 5 -> {value: 15, done: true}
it.next(); // {value: undefined, done: true}
Mixins vs inheritance
One extends; a mixin is a class factory B => class extends B
{…} — extends Ser(Stamped(Model)) keeps super. Swift: protocol extensions.
Interview traps
- Instances link to
Dog.prototype;Dogitself links toAnimal. P.prototype.tags = []: one array shared by every instance.- Spread/
assigncopy own enumerable props, run getters, drop the prototype.
Remember
Own first, then up the chain · writes shadow · .prototype is for instances ·
freeze is shallow · Reflect is the default trap.
Likely questions
- Why pass
receiver? — inherited getters must see the proxy asthis. - Generator vs async? — async = generator yielding promises + auto-runner.