The prototype chain
Learn how JavaScript property lookups walk from an object to its prototype, how shadowing works, how to tell own from inherited properties, and why this still points at the receiver in inherited methods.
- 01Trace a lookupFollow
rabbit → animal → Object.prototype → nulland predict the result. - 02Tell own from inheritedChoose the right check:
Object.hasOwn,in,hasOwnProperty,isPrototypeOf, orpropertyIsEnumerable. - 03Use inherited methods safelyExplain why
thisin an inherited method is the object before the dot.
Prototype links
Every ordinary JavaScript object has an internal link called [[Prototype]]. When you read a property and the object does not own that property, JavaScript follows the link to another object and tries again. That repeated search is the prototype chain.
This builds on Object basics, where you met in, Object.hasOwn, and inherited toString; Constructors & new, where new links instances to Constructor.prototype; Methods & this and The this keyword, where the call-site chooses this; and Scope, where the scope chain finds variables. Scope is for names. Prototypes are for properties.
Ask a shop for batteries. The clerk checks the shelf. If the shelf is empty, they call the warehouse. If the warehouse is empty, the warehouse calls the factory. Eventually someone has the item, or the answer is “sorry.” Property lookup feels the same.
- In real life: The shop shelf
- In JavaScript: The object you read from
- In real life: The warehouse
- In JavaScript: That object's prototype
- In real life: The factory behind the warehouse
- In JavaScript: The prototype's prototype
- In real life: Sorry, we don't stock it
- In JavaScript: Lookup reaches
null, so the value isundefined
Where the analogy stops: A shop clerk might order new stock for you. JavaScript lookup never invents a property; it only checks each object in order.
__proto__The legacy __proto__ accessor usually lives on Object.prototype, so normal objects can use it to read or set their prototype link. Objects created with Object.create(null) do not inherit that accessor. Prefer Object.getPrototypeOf, Object.setPrototypeOf, and Object.create. The object literal form { __proto__: animal } is standardized and shown below.
__proto__ syntaxconst animal = { eats: true };const rabbit = { __proto__: animal, jumps: true };console.log(Object.getPrototypeOf(rabbit) === animal);console.log(rabbit.eats);Lookup & shadowing
STEP THROUGHStart with two objects. rabbit owns jumps. It does not own eats, but its prototype, animal, does. It also inherits familiar methods like toString from Object.prototype, which sits farther up the chain.
Predict each lookup. Then step through the real path from rabbit to animal, to Object.prototype, and finally null.
script
const rabbit = Object.create(animal);rabbit.jumps = true; console.log(rabbit.jumps);console.log(rabbit.eats);console.log(typeof rabbit.toString);console.log(rabbit.fly);Reading and writing are not mirror images. Reads climb the chain. Ordinary writes place a new property on the receiver, which can shadow an inherited property with the same name.
Watch the read/write difference: reads climb the chain, but ordinary writes create an own property on the receiver.
script
const rabbit = Object.create(animal); rabbit.eats = false;console.log(rabbit.eats);console.log(animal.eats); delete rabbit.eats;console.log(rabbit.eats);If the prototype has an inherited setter for that name, assignment calls the setter instead of creating a plain data property. The setter still receives this as the object you assigned through.
const animal = { set mood(value) { this.savedMood = value; },};const rabbit = Object.create(animal);rabbit.mood = "curious";console.log(rabbit.savedMood);console.log(Object.hasOwn(rabbit, "mood"));Own vs inherited properties
INTERACTIVEAn own property is stored directly on the object. An inherited property is found by following the prototype chain. Both are read with the same dot syntax, so you need deliberate checks when ownership matters.
const animal = { eats: true, walk() {} };const rabbit = Object.create(animal);rabbit.jumps = true; let current = rabbit;while (current !== null) { // Check own properties, then climb. current = Object.getPrototypeOf(current);}- rabbit
no own eats - animal
eats: true
Lookup stops at the first owner.
Lookup found eats on animal with value true.
Object.getPrototypeOf and checks ownership with Object.hasOwn.You can cook from the family recipe without owning a copy. If you write your own version and put it on your fridge, you use that first. Remove your note, and the family binder is visible again.
- In real life: A recipe in the family binder
- In JavaScript: An inherited method or value
- In real life: Your handwritten variation
- In JavaScript: An own property that shadows it
- In real life: Throwing away your variation
- In JavaScript:
deleterevealing the inherited value again
Where the analogy stops: Family members might physically copy the recipe. JavaScript does not copy inherited properties during lookup.
- rabbit.jumps after
rabbit.jumps = true - rabbit.eats when only
animal.eatsexists - rabbit.toString from ordinary objects
- rabbit.fly when no object in the chain has it
- rabbit.eats after
rabbit.eats = false - rabbit.secret inherited from
animal
Sort each read by where JavaScript finds the property in the lesson's rabbit → animal chain.
hasOwnProperty, isPrototypeOf & propertyIsEnumerable
Different tools answer different questions. Use the exact question you mean: “does this object own it?”, “can lookup find it?”, “is this object in the chain?”, or “will enumeration include it?”
| Tool | Answers | Use it when |
|---|---|---|
Object.hasOwn(obj, key) | Own only | Safe even if obj has no prototype or shadows hasOwnProperty |
obj.hasOwnProperty(key) | Own only | Works on normal objects, but can be missing or shadowed |
key in obj | Own or inherited | Good for availability, not ownership |
proto.isPrototypeOf(obj) | Prototype relationship | Asks whether proto appears anywhere in obj's chain |
obj.propertyIsEnumerable(key) | Own and enumerable | False for inherited properties and non-enumerable own properties |
Object.keys(obj) | Own enumerable strings | Does not include inherited keys |
for...in | Enumerable strings, own and inherited | Often pair with Object.hasOwn inside the loop |
const animal = { eats: true };Object.defineProperty(animal, "secret", { value: "warehouse", enumerable: false,});const rabbit = Object.create(animal);rabbit.jumps = true; console.log(Object.hasOwn(rabbit, "jumps"));console.log(Object.hasOwn(rabbit, "eats"));console.log("eats" in rabbit);console.log(Object.keys(rabbit).join(","));console.log(animal.isPrototypeOf(rabbit));console.log(animal.propertyIsEnumerable("secret"));Object.hasOwn is safer than obj.hasOwnProperty because it still works for null-prototype objects and objects that define their own property named hasOwnProperty. propertyIsEnumerable is stricter: the property must be own and enumerable.
this in inherited methods
STEP THROUGHA method can be found on a prototype but still act on the object before the dot. In rabbit.walk(), lookup finds walk on animal, but the call-site sets this to rabbit. The method below is inline on purpose so production minifiers cannot detach it and change the this demo.
Step through an inherited method. The important question is not where the method was found, but which object was before the dot when it was called.
script
walk() { this.isWalking = true; },};const rabbit = Object.create(animal); rabbit.walk();console.log(rabbit.isWalking);console.log(animal.isWalking);This is why shared prototype methods are useful: one function can serve many objects, while state such as isWalking lands on the receiver.
Where you’ll use this
Prototype lookup explains why arrays all have map, why dates all have toISOString, why constructor instances share methods, and why classes can store methods once instead of copying them to every object. The next lesson, F.prototype & constructors, connects new to this link.
It also prevents bugs. A mutable object stored on a prototype is shared by every inheriting object. Mutating it through one child mutates the shared value for all children.
const animal = { skills: [] };const rabbit = Object.create(animal);const fox = Object.create(animal); rabbit.skills.push("jump");fox.skills.push("sneak");console.log(rabbit.skills.join(","));const animal = { skills: [] };const rabbit = Object.create(animal);const fox = Object.create(animal); rabbit.skills = [];fox.skills = [];rabbit.skills.push("jump");fox.skills.push("sneak");console.log(rabbit.skills.join(","));Common misconceptions
“Inherited properties are copied onto the object.”
No copy happens. The object keeps a link, and lookup follows it when needed.
“in means own.”
in means lookup can find the property, including inherited ones. Use Object.hasOwn for ownership.
“Changing a child changes the prototype.”
Ordinary assignment creates or updates the child’s own property. Setters are the key exception.
“The scope chain and prototype chain are the same.”
Scope resolves variable names. Prototypes resolve object properties.
“__proto__ is modern API style.”
The literal form is standard, but the accessor is legacy. Prefer the Object methods.
“Putting arrays on prototypes is harmless.”
Methods are great to share. Mutable data like arrays is usually per-object state, not prototype state.
Practice: trace the chain
5 EXERCISESPredict what the program prints.
const animal = { eats: true };
const rabbit = Object.create(animal);
rabbit.eats = false;
delete rabbit.eats;
console.log(rabbit.eats);It prints true. After deleting the shadowing own property, lookup climbs to animal.eats.
Type both outputs, separated by a comma.
const animal = { eats: true, sleeps: true };
const rabbit = Object.create(animal);
rabbit.jumps = true;
rabbit.eats = false;
console.log(Object.keys(rabbit).length);
console.log("sleeps" in rabbit);It prints 2 and true. rabbit owns jumps and its shadowing eats, while sleeps is inherited but still found by in.
The starter prints jump,sneak. Change it so rabbit.skills prints only jump.
const animal = { skills: [] };
const rabbit = Object.create(animal);
const fox = Object.create(animal);
rabbit.skills.push("jump");
fox.skills.push("sneak");
console.log(rabbit.skills.join(","));const animal = { skills: [] };
const rabbit = Object.create(animal);
const fox = Object.create(animal);
rabbit.skills = [];
fox.skills = [];
rabbit.skills.push("jump");
fox.skills.push("sneak");
console.log(rabbit.skills.join(","));Assigning rabbit.skills = [] and fox.skills = [] creates separate own arrays. Now each push mutates only that object’s array.
Which method tells you whether animal is in rabbit’s prototype chain?
animal.isPrototypeOf(rabbit);isPrototypeOf answers whether animal appears anywhere in rabbit’s prototype chain.
this in an inherited methodAfter rabbit.walk() in the lesson’s method example, where is isWalking created?
isWalking is created on rabbit. Lookup finds walk on animal, but the call-site rabbit.walk() makes this equal to rabbit.
Quiz: check your understanding
7 QUESTIONSRead each code sample as a chain trace: where is the property stored, and does this operation read, write, delete, or call?
Question 1 of 7What is an object's
[[Prototype]]?Choose an answer to see the explanation.
Question 2 of 7What does this shadowing example print?
Read the code, then predictconst animal = { eats: true }; const rabbit = Object.create(animal); rabbit.eats = false; console.log(animal.eats);Choose an answer to see the explanation.
Question 3 of 7Which check includes inherited properties?
Choose an answer to see the explanation.
Question 4 of 7What does this inherited method example print?
Read the code, then predictconst animal = { walk() { this.isWalking = true; } }; const rabbit = Object.create(animal); rabbit.walk(); console.log(Object.hasOwn(rabbit, "isWalking"));Choose an answer to see the explanation.
Question 5 of 7Which statement about
__proto__is accurate?Choose an answer to see the explanation.
Question 6 of 7What does this shared array example print?
Read the code, then predictconst animal = { skills: [] }; const rabbit = Object.create(animal); const fox = Object.create(animal); rabbit.skills.push("jump"); fox.skills.push("sneak"); console.log(rabbit.skills.join(","));Choose an answer to see the explanation.
Question 7 of 7What does
Object.keys(rabbit)include?Choose an answer to see the explanation.
Key takeaways
[[Prototype]]is an internal object link used for property lookup.- Lookup checks own properties first, then prototypes, and returns
undefinedafternull. - Shadowing happens when an own property hides an inherited one.
Object.hasOwnchecks ownership;inchecks availability through the chain.- Inherited methods get
thisfrom the call-site, sorabbit.walk()writes torabbit.
One-liner.
The prototype chain is JavaScript’s property fallback path: object first, prototypes next, null last.
Up next: F.prototype & constructors.