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.
- 01Follow a property readFollow
rabbit → animal → Object.prototype → nulland say what each read gives back. - 02Tell own from inheritedPick 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 a hidden link to one other object. The link is called [[Prototype]]. The double square brackets mean the link lives inside JavaScript. You cannot write rabbit.[[Prototype]] in your code.
When you read a property, JavaScript first looks on the object itself. If the property is not there, JavaScript follows the link to the other object and looks there. It keeps going, one link at a time. This path of linked objects is the prototype chain. The object at the other end of a link is called a prototype.
This lesson uses ideas from earlier lessons:
- Object basics: the
inoperator,Object.hasOwn, and thetoStringmethod that every object seems to have. - Constructors & new:
newlinks each new object toConstructor.prototype. - Methods & this, and The this keyword: the object before the dot becomes
this. - Scope: how JavaScript finds variables.
Scope finds variables, like rabbit. The prototype chain finds properties, like rabbit.eats.
You ask a shop for batteries. The clerk checks the shelf. The shelf is empty, so the clerk calls the warehouse. The warehouse is empty too, so it calls the factory. In the end, someone has batteries, or the answer is "Sorry, we don't stock it."
- 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: The search reaches
null, so the value isundefined
__proto__ nameOlder code reads and changes the link with obj.__proto__. This is an old accessor: a property that runs code when you read or set it. It lives on Object.prototype, so normal objects can use it. Objects made with Object.create(null) do not have it. In new code, use Object.getPrototypeOf, Object.setPrototypeOf, and Object.create. One form is still fine: writing { __proto__: animal } inside an object literal is part of the standard. The next example uses it.
const animal = { eats: true };const rabbit = { __proto__: animal, jumps: true };console.log(Object.getPrototypeOf(rabbit) === animal);console.log(rabbit.eats);Line 2 makes rabbit and points its link at animal. Line 3 prints true: Object.getPrototypeOf(rabbit) gives back the object at the other end of the link, which is animal. Line 4 prints true: rabbit has no eats, so JavaScript finds it on animal.
rabbit has no eats of its own. JavaScript follows the link to animal and finds eats: true there.Three Object methods work with the link:
Object.getPrototypeOf(obj)gives you the object at the other end of the link.Object.create(proto)makes a new, empty object whose link points atproto.Object.setPrototypeOf(obj, proto)changes the link of an object that already exists.
const animal = { eats: true };const rabbit = Object.create(animal); console.log(Object.getPrototypeOf(rabbit) === animal);Object.setPrototypeOf(rabbit, null);console.log(rabbit.eats);console.log(Object.getPrototypeOf(rabbit) === null);Line 4 prints true: the link points at animal. Line 5 sets the link to null, so now there is no next object. Line 6 prints undefined: rabbit has no eats, and there is nowhere left to look. Line 7 prints true.
JavaScript engines (the programs that run your code) work faster when an object keeps the same link. So set the link when you create the object, if you can.
Here is the simplest everyday use. One object holds a method, and another object uses it through the link.
const person = { greet() { return "Hello"; },};const user = Object.create(person);console.log(user.greet());console.log(Object.hasOwn(user, "greet"));Line 6 makes user with its link pointing at person. Line 7 calls user.greet(). user has no greet, so JavaScript finds it on person and prints Hello. Line 8 prints false: greet was not copied onto user.
Lookup & shadowing
STEP THROUGHAn object owns a property when the property is stored on the object itself. A property that JavaScript finds through the link is inherited. The step-through below uses two objects:
animalownseatsand awalkmethod.rabbitownsjumps. Its link points atanimal.
rabbit also finds toString. That method lives on Object.prototype, the built-in object that sits after animal in the chain.
Guess what each line prints. Then step through and watch JavaScript go from rabbit to animal, to Object.prototype, and finally to 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);A chain can have more than two objects. Here JavaScript checks dog, then animal, then Object.prototype.
const animal = { sleeps: true };const dog = Object.create(animal);dog.barks = true; console.log(dog.barks);console.log(dog.sleeps);console.log(Object.getPrototypeOf(Object.getPrototypeOf(dog)) === Object.prototype);Line 5 prints true from dog itself. Line 6 prints true from animal. Line 7 calls Object.getPrototypeOf twice. The first call gives animal. The second gives the prototype of animal, which is Object.prototype, so it prints true.
When a property is missing, JavaScript does not stop at the first object. It checks every object in the chain. Only when the next link is null does it give back undefined.
const animal = { sleeps: true };const dog = Object.create(animal); console.log(dog.flies);console.log("flies" in dog);Line 4 prints undefined. Line 5 prints false: the in operator also searched the whole chain and found nothing.
dog, then animal, then Object.prototype. None of them has flies, and the next link is null, so the answer is undefined.Reading and writing work differently:
- A read climbs the chain until it finds the property.
- A normal write, like
rabbit.eats = false, stores the property on the object before the dot. It does not change the prototype. - If an object owns a property with the same name as one further up the chain, JavaScript finds the own one first. We say the own property shadows the inherited one: it hides it.
Watch how reads and writes differ. A read climbs the chain. A normal write puts the property on the object before the dot.
script
const rabbit = Object.create(animal); rabbit.eats = false;console.log(rabbit.eats);console.log(animal.eats); delete rabbit.eats;console.log(rabbit.eats);eats on rabbit. A read of rabbit.eats now stops at rabbit, and animal.eats stays true.A setter is a function that runs when you assign to a property. You write it with set. If the prototype has a setter for that name, the assignment calls the setter. It does not make a new property. Inside the setter, this is still the object you assigned to.
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"));Line 7 runs the setter from animal, with this set to rabbit. So line 8 prints curious. Line 9 prints false: rabbit never got its own mood.
delete removes only a property that the object owns. It never touches the prototype. Once the own property is gone, the inherited one shows again.
const person = { name: "Person" };const user = Object.create(person);user.name = "Asha";delete user.name; console.log(user.name);console.log(Object.hasOwn(user, "name"));Line 3 gives user its own name. Line 4 deletes it. Line 6 prints Person, found on person. Line 7 prints false.
One more case. A property can be marked non-writable, which means its value cannot be changed. Object.defineProperty makes a property with settings like this. If the prototype has a non-writable property, a normal assignment to that name does nothing in a script without "use strict". It does not create an own property.
const animal = {};Object.defineProperty(animal, "id", { value: 1, writable: false });const rabbit = Object.create(animal); rabbit.id = 2;console.log(rabbit.id);console.log(Object.hasOwn(rabbit, "id"));Line 6 prints 1: the inherited id did not change. Line 7 prints false: rabbit has no own id. In strict mode, the same assignment throws a TypeError instead. Either way, the write did not create a shadow.
Own vs inherited properties
INTERACTIVEAn own property is stored on the object itself. An inherited property is found by following the link. You read both the same way, with a dot. So when it matters where a property is stored, you need a check.
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 eats here - animal
eats: true
JavaScript stops at the first object that has it.
JavaScript found eats on animal. Its value is true.
Object.getPrototypeOf and checks each object with Object.hasOwn.You can cook from the family recipe without having your own copy. If you write your own version and stick it on your fridge, you cook from that one first. Throw your note away, and you cook from the family binder again.
- In real life: A recipe in the family binder
- In JavaScript: An inherited method or value
- In real life: Your handwritten version
- In JavaScript: An own property that shadows it
- In real life: Throwing away your version
- In JavaScript:
deleteshows the inherited value again
- rabbit.jumps after
rabbit.jumps = true - rabbit.eats when only
animal.eatsexists - rabbit.toString, which every object seems to have
- rabbit.fly when no object in the chain has it
- rabbit.eats after
rabbit.eats = false - rabbit.secret, a hidden property on
animal
Sort each read by where JavaScript finds the property. The chain is rabbit, then animal, then Object.prototype.
Where a property is stored also changes which keys you get when you list them:
Object.keys(obj)lists only the keys the object owns.for...inalso lists keys from the prototypes.- Both skip keys that are marked non-enumerable, which means hidden from listing.
If you want only own keys in a for...in loop, check each key with Object.hasOwn.
const animal = { country: "India" };const rabbit = Object.create(animal);rabbit.name = "Asha"; console.log(Object.keys(rabbit).join(","));const keys = [];for (const key in rabbit) keys.push(key);console.log(keys.join(","));Line 5 prints name. Lines 6 to 8 collect keys with for...in and print name,country. The key country came from animal.
Object.create(null) makes an object with no link at all, so its chain ends right away. This is handy for a dictionary: an object you use only to store keys and values. No extra names like toString come along. But it has no helper methods either. So call Object.hasOwn(settings, key), because settings.hasOwnProperty does not exist.
const settings = Object.create(null);settings.theme = "light"; console.log(Object.hasOwn(settings, "theme"));console.log("toString" in settings);console.log(typeof settings.toString);Line 4 prints true. Line 5 prints false, and line 6 prints undefined: there is no Object.prototype in the chain, so there is no toString to find.
hasOwnProperty, isPrototypeOf & propertyIsEnumerable
Each check answers a different question. Pick the one that matches what you want to know:
- Is the property stored on this object?
- Can JavaScript find the property anywhere in the chain?
- Is this object somewhere in that object's chain?
- Will the property show up when I list the keys?
Each row of the table is one check. Read across: what it answers, then when to use it.
| Check | What it answers | When to use it |
|---|---|---|
Object.hasOwn(obj, key) | Is key stored on obj itself? | Use it to check ownership. It works even when obj has no prototype. |
obj.hasOwnProperty(key) | Is key stored on obj itself? | It works on normal objects. It is missing on objects with no prototype. |
key in obj | Can JavaScript find key anywhere in the chain? | Use it when an inherited property also counts. |
proto.isPrototypeOf(obj) | Is proto somewhere in the chain of obj? | Use it to check how two objects are linked. |
obj.propertyIsEnumerable(key) | Is key stored on obj and shown in lists? | It is false for inherited keys and for hidden keys. |
Object.keys(obj) | Which keys does obj store and show? | It leaves out inherited keys. |
for...in | Which keys show up, here and up the chain? | Add an Object.hasOwn check inside to keep only own keys. |
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"));Lines 2 to 5 add a secret property to animal. enumerable: false hides it from listing. Then the six lines print:
- Line 9:
true.rabbitownsjumps. - Line 10:
false.eatsis onanimal, not onrabbit. - Line 11:
true.infindseatsthrough the link. - Line 12:
jumps. Only the own key is listed. - Line 13:
true.animalis in the chain ofrabbit. - Line 14:
false.secretis hidden from listing.
Object.hasOwn is safer than obj.hasOwnProperty. It still works when the object has no prototype, and when the object has its own property named hasOwnProperty. propertyIsEnumerable is stricter than both. It is true only when the object owns the property and the property is not hidden from listing.
this in inherited methods
STEP THROUGHA method can be found on a prototype and still work on the object before the dot. In rabbit.walk(), JavaScript finds walk on animal. But rabbit is before the dot, so this is rabbit.
The method is written inside the object on purpose. A minifier is a tool that makes code smaller before it goes live. It could move a separate function around and change what this shows in this demo.
Step through a method that lives on the prototype. What matters is not where JavaScript found the method, but which object was before the dot.
script
walk() { this.isWalking = true; },};const rabbit = Object.create(animal); rabbit.walk();console.log(rabbit.isWalking);console.log(animal.isWalking);walk on animal. But rabbit is before the dot, so this is rabbit, and isWalking lands on rabbit.This is why methods on a prototype are useful. One function can serve many objects, and each object keeps its own data, such as isWalking.
Where you’ll use this
The prototype chain explains things you use every day:
- Every array has
map, becausemaplives onArray.prototype. - Every date has
toISOString, because it lives onDate.prototype. - Objects made with the same constructor share its methods.
- A class stores each method once, not on every object.
The next lesson, F.prototype & constructors, shows how new sets this link.
Built-in objects use the same chain. An array links to Array.prototype. A function links to Function.prototype. Both of those link to Object.prototype.
const scores = [10, 20];function greet() {} console.log(Object.getPrototypeOf(scores) === Array.prototype);console.log(Object.getPrototypeOf(greet) === Function.prototype);console.log(Object.getPrototypeOf(Array.prototype) === Object.prototype);The chain can also cause bugs. If a prototype holds an array, every linked object uses that same array. Changing it through one object changes it for all of them.
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(","));On line 5, rabbit has no own skills, so JavaScript finds the array on animal and pushes into it. Line 6 does the same for fox. Line 7 prints jump,sneak.
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(","));Lines 5 and 6 give each object its own new array. Now line 9 prints jump.
A factory is a function that makes and returns new objects. It can keep the shared methods on one prototype and give each new object its own data. This is usually clearer than changing links after the objects exist.
const userMethods = { greet() { return "Hi " + this.name; },};function makeUser(name) { const user = Object.create(userMethods); user.name = name; return user;}const user = makeUser("Asha");console.log(user.greet());console.log(Object.hasOwn(user, "greet"));Line 12 prints Hi Asha. Line 13 prints false: greet is inherited, while name is own. Class objects work the same way: the methods sit on one prototype, and each object keeps its own data.
Avoid Object.setPrototypeOf on objects that are already in use. Engines read properties faster when an object's link stays the same, and changing the link can undo that speed-up. Also be careful when you copy keys from user input into objects. A key such as __proto__ can reach a shared prototype and change what many objects inherit. This attack is called prototype pollution.
Common misconceptions
“Inherited properties are copied onto the object.”
Nothing is copied. The object keeps a link, and JavaScript follows the link when it needs to.
“in means the object owns the property.”
in is true when JavaScript can find the property anywhere in the chain. To check whether the object itself stores it, use Object.hasOwn.
“Assigning to an object changes its prototype.”
A normal assignment creates or changes a property on the object itself. The prototype stays the same. The one exception is an inherited setter.
“The scope chain and the prototype chain are the same.”
Scope finds variables, like rabbit. The prototype chain finds properties, like rabbit.eats.
“obj.__proto__ is the modern way to use the link.”
Writing __proto__ inside an object literal is standard. But reading or setting obj.__proto__ is old. Use the Object methods instead.
“Putting an array on a prototype is harmless.”
Sharing methods is good. But an array on a prototype is shared too, so every linked object changes the same array. Give each object its own array.
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 the own eats is deleted, JavaScript finds animal.eats again.
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 own eats. sleeps is on animal, but in still finds it.
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(","));rabbit.skills = [] and fox.skills = [] give each object its own array. Now each push changes only that object’s array.
Which method tells you whether animal is in the chain of rabbit?
animal.isPrototypeOf(rabbit);animal.isPrototypeOf(rabbit) is true when animal is anywhere in the chain of rabbit.
this in an inherited methodAfter rabbit.walk() in the lesson's method example, where is isWalking created?
On rabbit. JavaScript finds walk on animal, but rabbit is before the dot, so this is rabbit.
Quiz: check your understanding
10 QUESTIONSFor each code sample, ask two questions. Where is the property stored? Does the code read, write, delete, or call it?
Question 1 of 10What is an object's
[[Prototype]]?Choose an answer to see the explanation.
Question 2 of 10After the write to
rabbit.eats, what does this 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 10Which check also counts inherited properties?
Choose an answer to see the explanation.
Question 4 of 10What does this inherited method program 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 10Which sentence about
__proto__is true?Choose an answer to see the explanation.
Question 6 of 10What does this shared array program 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 10What does
Object.keys(rabbit)include?Choose an answer to see the explanation.
Question 8 of 10What does this
Object.keysprogram print?Read the code, then predictconst animal = { country: "India" }; const rabbit = Object.create(animal); rabbit.name = "Asha"; console.log(Object.keys(rabbit).join(","));Choose an answer to see the explanation.
Question 9 of 10What does an object with no prototype print here?
Read the code, then predictconst settings = Object.create(null); console.log(typeof settings.toString);Choose an answer to see the explanation.
Question 10 of 10The prototype has a property that cannot be changed. What does this print?
Read the code, then predictconst animal = {}; Object.defineProperty(animal, "id", { value: 1, writable: false }); const rabbit = Object.create(animal); rabbit.id = 2; console.log(rabbit.id);Choose an answer to see the explanation.
Key takeaways
[[Prototype]]is a hidden link from one object to another. JavaScript uses it to find properties.- A read checks the object first, then each prototype in turn. If the next link is
null, the result isundefined. - An own property with the same name hides (shadows) the inherited one.
Object.hasOwnasks "Is it stored here?"inasks "Can JavaScript find it anywhere in the chain?"- An inherited method gets
thisfrom the object before the dot, sorabbit.walk()writes torabbit.
One-liner.
The prototype chain is where JavaScript looks for a property: the object first, then its prototypes, and null at the end.
Up next: F.prototype & constructors.