Suggest an editImprove this articleRefine the answer for “Property lookup in the prototype chain”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**When you read `obj.prop`, the engine first looks for an own property on the object itself, then on its prototype (`Object.getPrototypeOf(obj)`), then on the prototype's prototype and so on upwards, until it finds the property or reaches `null`.** If the property is nowhere to be found, the result is `undefined`. That sequence is what we call the prototype chain. The lookup is dynamic: properties are never copied anywhere. Writing works differently: `obj.prop = value` always creates or overwrites an own property on `obj` itself and leaves the prototype untouched, except when a setter is found in the chain. ```javascript const animal = { eats: true }; const dog = Object.create(animal); console.log(dog.eats); // true, found on animal console.log(dog.fly); // undefined, nowhere to be found ``` **Key point:** reading walks up the chain, writing stops at the object itself.Shown above the full answer for quick recall.Answer (EN)Image**The prototype chain is the sequence of objects that the JavaScript engine walks upwards while looking for a property that the object itself does not have.** The lookup starts with own properties, continues with the prototype, then the prototype's prototype, and ends at `null`. ## Theory ### TL;DR - Reading `obj.prop`: own properties of `obj` first, then `obj.__proto__`, then higher up, until `null`. - Found nowhere: the result is `undefined`, no error is thrown. - The lookup is dynamic: properties are not copied, the engine walks the chain every time (with caching). - Writing `obj.prop = value` always creates an own property on `obj`, the prototype stays unchanged. - Getters and setters found in the chain are executed, not returned as values. - `Object.create(null)` gives an object with an empty chain: the lookup stops immediately. ### Quick example ```javascript const animal = { eats: true }; const dog = Object.create(animal); dog.barks = true; console.log(dog.barks); // 1) found on dog -> true console.log(dog.eats); // 2) not on dog -> look at animal -> true console.log(dog.toString); // 3) not on animal -> Object.prototype -> [Function] console.log(dog.fly); // 4) nowhere -> undefined ``` The chain here is: ```javascript dog -> animal -> Object.prototype -> null ``` Visually it is a stack of objects where each next one is the "fallback" for the previous: ```javascript dog { barks: true } ^ | animal { eats: true } ^ | Object.prototype { toString(), hasOwnProperty(), ... } ^ | null ``` ### The algorithm under the hood, step by step When `obj.prop` runs, the engine does the following: 1. Checks whether `prop` is an **own** property of `obj`. If so, it returns the value and stops. 2. If not, it takes the object's prototype: ```javascript const parent = Object.getPrototypeOf(obj); ``` 3. Checks whether `prop` exists on `parent`. 4. Repeats steps 2 and 3 until either: - the property is found, or - the prototype equals `null`. If the loop reaches `null`, `undefined` is returned. Importantly, all of this happens **dynamically**, without copying properties: if you add a property to the prototype later, every descendant sees it right away. ### Writing a property works differently For `obj.prop = value` the mechanism is different: - If the property **already exists on the object itself**, it is simply overwritten. - If not, a **new own property is created on `obj`**, not on the prototype. - The prototype is not modified at all. ```javascript const animal = { eats: true }; const dog = Object.create(animal); dog.eats = false; // creates a new own property on dog console.log(dog.eats); // false, own property console.log(animal.eats); // true, on the prototype, unchanged ``` This overriding is called shadowing: an own property shadows the prototype property of the same name. ### Reading and writing take different paths | Operation | Where it looks | What it does | | --- | --- | --- | | `obj.prop` (read) | on `obj`, then up the chain | returns the first value found | | `obj.prop = value` (write) | writes to `obj` when `writable: true` | does not change the prototype | | `delete obj.prop` | on `obj` only | does not affect the prototype | That is exactly why `delete dog.eats` after shadowing restores access to `animal.eats`: the own property is removed while the prototype one stays in place. ### Getters and setters in the chain If a **getter** (`get prop()`) is found at any level of the chain, it is **executed** rather than returned as a function. Likewise, a **setter** that is found runs on assignment, and in that case no new own property is created on the object. ```javascript const animal = { get info() { return 'Animal info'; } }; const dog = Object.create(animal); console.log(dog.info); // "Animal info", the prototype's getter was called ``` So prototypes can hold not only data but computed, "reactive" properties too. ### Deep chains and engine optimisation The chain has no length limit, the lookup simply walks more levels: ```javascript const a = { level: 'A' }; const b = Object.create(a); const c = Object.create(b); const d = Object.create(c); console.log(d.level); // "A" ``` Here the engine inspects four objects before it finds `level` on `a`. In practice this is cheaper than it sounds, because JS engines (V8, SpiderMonkey and others) aggressively optimise prototype lookup: - they build internal "shape" maps, that is, descriptions of object structure; - they cache where exactly a property was found (inline caches); - they drop the cache as soon as the prototype or the set of properties changes. That is why changing a prototype at runtime with `Object.setPrototypeOf` is a slow operation: it invalidates caches and forces the engine to deoptimise the code. Set the prototype at creation time instead: `Object.create(proto)` or `class ... extends`. ### An object with no prototype ```javascript const pure = Object.create(null); pure.key = 'value'; console.log(pure.toString); // undefined, there is no Object.prototype ``` The chain ends immediately: ```javascript pure -> null ``` The lookup stops instantly. This is why `Object.create(null)` is handy for dictionaries: no key such as `toString` or `constructor` arrives from a prototype and spoils an `if (dict[key])` check. ### Summary | Stage | What happens | | --- | --- | | 1 | The engine looks for the property on the object itself | | 2 | If not found, it goes to `[[Prototype]]` | | 3 | It keeps climbing the chain | | 4 | Reached `null` - returns `undefined` | | Write | Creates an own property on the object, not on the prototype | | Accessors | Getters and setters in the chain are taken into account and executed | ### Common mistakes - **Thinking properties are copied into the object.** Nothing is copied: `dog` does not contain `eats`, it merely finds it on `animal` on every read. - **Expecting `dog.eats = false` to change the prototype.** A write always creates an own property, `animal.eats` stays `true`. - **Confusing `in` with `hasOwnProperty`.** `'eats' in dog` is `true` (it searches the whole chain), while `Object.hasOwn(dog, 'eats')` is `false`, because it checks own properties only. - **Iterating with `for...in` without a filter.** That loop also visits inherited enumerable properties; use `Object.keys` or `Object.hasOwn`. - **Changing the prototype on the fly with `Object.setPrototypeOf`.** It is correct but slow: the engine drops its caches. Set the prototype at creation time. - **Extending built-in prototypes.** Adding methods to `Object.prototype` or `Array.prototype` puts them in the chain of absolutely every object and breaks other people's code. - **Forgetting about `Object.create(null)`.** Such an object has no `toString`, `hasOwnProperty` and similar methods, so `pure.hasOwnProperty('key')` throws.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.