Understand the chain JavaScript follows when it looks for a property, and how objects inherit from other objects.

Bishal Biswas
Self Learner
Where do built in methods come from?
const name = 'ravi';
console.log(name.toUpperCase());
const list = [3, 1, 2];
console.log(list.sort());
You never wrote toUpperCase or sort. So where do they live?
Every object has a hidden link to another object, called its prototype. When you ask for a property that the object does not have, JavaScript follows that link and looks there instead.
The prototype chain
const dog = { name: 'Bruno' };
console.log(dog.name);
console.log(dog.toString());
console.log(dog.notThere);
dog has no toString, yet it works. The lookup goes:
- Look on
dogitself. Not found. - Follow the link to
Object.prototype. FoundtoString. Use it.
For notThere, the search reaches the end of the chain and returns undefined.
console.log(Object.getPrototypeOf(dog) === Object.prototype);
console.log(Object.getPrototypeOf(Object.prototype));
The chain always ends at null. That is the top.
For arrays there is one more link:
const list = [1, 2, 3];
console.log(Object.getPrototypeOf(list) === Array.prototype);
console.log(Object.getPrototypeOf(Array.prototype) === Object.prototype);
So list finds sort on Array.prototype, and toString further up on Object.prototype.
Setting a prototype yourself
Object.create makes a new object with the prototype you choose.
const animal = {
eats: true,
describe() {
return `${this.name} eats: ${this.eats}`;
}
};
const rabbit = Object.create(animal);
rabbit.name = 'Rabbit';
console.log(rabbit.eats);
console.log(rabbit.describe());
console.log(Object.hasOwn(rabbit, 'eats'));
rabbit has only name of its own. eats and describe come from animal through the chain.
Own properties versus inherited
const rabbit = Object.create({ eats: true });
rabbit.name = 'Rabbit';
console.log(Object.hasOwn(rabbit, 'name'));
console.log(Object.hasOwn(rabbit, 'eats'));
console.log('eats' in rabbit);
console.log(Object.keys(rabbit));
Object.hasOwnchecks the object itself only.insearches the whole chain.Object.keyslists own properties only.
This distinction matters when you loop:
const rabbit = Object.create({ eats: true });
rabbit.name = 'Rabbit';
for (const key in rabbit) {
console.log(key);
}
for...in walks inherited properties too. That is another reason to prefer Object.keys or for...of.
Writing does not go up the chain
Reading follows the prototype. Writing always lands on the object itself.
const animal = { eats: true };
const rabbit = Object.create(animal);
console.log(rabbit.eats);
rabbit.eats = false;
console.log(rabbit.eats);
console.log(animal.eats);
rabbit now has its own eats that shadows the inherited one. The prototype is untouched.
prototype versus __proto__
Two similar names that confuse everyone.
Constructor.prototypeis a property on a function. It is the object that instances made withnewwill link to.- The internal link on an instance points to that object. You read it with
Object.getPrototypeOf(obj).
function User(name) {
this.name = name;
}
User.prototype.greet = function () {
return `Hi, ${this.name}`;
};
const asha = new User('Asha');
console.log(Object.getPrototypeOf(asha) === User.prototype);
console.log(asha.greet());
You may see asha.__proto__ in older code and tutorials. It does the same job but is deprecated. Use Object.getPrototypeOf and Object.setPrototypeOf.
Building a chain by hand
const animal = {
init(name) {
this.name = name;
return this;
},
speak() {
return `${this.name} makes a sound`;
}
};
const dog = Object.create(animal);
dog.speak = function () {
return `${this.name} barks`;
};
const bruno = Object.create(dog).init('Bruno');
console.log(bruno.speak());
console.log(Object.getPrototypeOf(Object.getPrototypeOf(bruno)) === animal);
bruno finds speak on dog first, so the dog version wins. It finds init further up on animal. This is inheritance without a single class keyword.
Do not extend built in prototypes
Array.prototype.last = function () {
return this[this.length - 1];
};
console.log([1, 2, 3].last());
Tempting, but a bad idea. If two libraries add last with different behaviour, one silently breaks the other. If JavaScript itself later adds last, your version overrides the standard one. Write a normal function instead:
function last(array) {
return array[array.length - 1];
}
console.log(last([1, 2, 3]));
Performance
Long chains cost a little on lookup, because JavaScript walks each link. In practice, engines optimise this heavily and it is almost never worth worrying about. Keep chains shallow because it is easier to understand, not because of speed.
Quick recap
- Every object links to a prototype, and lookups travel up that chain.
- The chain ends at
Object.prototype, thennull. Object.create(proto)makes an object with the prototype you choose.- Reading follows the chain, writing always creates an own property.
Constructor.prototypeis on the function,Object.getPrototypeOfreads the link on an instance.- Use
Object.hasOwnfor own properties andinwhen inherited ones count. - Never add properties to built in prototypes.
// Every object links to a prototype, and lookups travel up the chain
const dog = { name: 'Bruno' };
console.log(dog.toString()); // found on Object.prototype
console.log(Object.getPrototypeOf(dog) === Object.prototype); // true
console.log(Object.getPrototypeOf(Object.prototype)); // null, the top
// Arrays have one extra link
const list = [1, 2, 3];
console.log(Object.getPrototypeOf(list) === Array.prototype); // true
// Object.create picks the prototype yourself
const animal = {
eats: true,
describe() { return this.name + ' eats: ' + this.eats; }
};
const rabbit = Object.create(animal);
rabbit.name = 'Rabbit';
console.log(rabbit.describe()); // Rabbit eats: true
console.log(Object.hasOwn(rabbit, 'eats')); // false, it is inherited
console.log('eats' in rabbit); // true, `in` searches the chain
console.log(Object.keys(rabbit)); // ['name'], own properties only
// Reading follows the chain, writing lands on the object itself
rabbit.eats = false;
console.log(rabbit.eats, animal.eats); // false true, the parent is untouched
// Constructor.prototype vs the link on an instance
function User(name) { this.name = name; }
User.prototype.greet = function () { return 'Hi, ' + this.name; };
const asha = new User('Asha');
console.log(Object.getPrototypeOf(asha) === User.prototype); // true
console.log(asha.greet());
// A chain built by hand: dog overrides speak, init comes from animal
const base = {
init(name) { this.name = name; return this; },
speak() { return this.name + ' makes a sound'; }
};
const dogProto = Object.create(base);
dogProto.speak = function () { return this.name + ' barks'; };
const bruno = Object.create(dogProto).init('Bruno');
console.log(bruno.speak()); // Bruno barks
// Do NOT do this: extending built in prototypes breaks other code
// Array.prototype.last = function () { return this[this.length - 1]; };
function last(array) { return array[array.length - 1]; }
console.log(last([1, 2, 3])); // 3
Was this explanation helpful?
Your feedback helps us improve the content for everyone.
Thank you for your feedback!
We appreciate you helping us improve.
What could we improve? (Optional)
Read aloud
Ready