All about prototypes
Prototype inheritance
Every object have a special hidden property [[Prototype]]
it is either null
or a reference to another object.
Whenever you read a property from object
and if it is missing, JavaScript will automatically take it from the prototype.
Setting prototype
One of the way to set the prototype is to use the special name __proto__
:
let animal = {
eat: true
};
let rabbit = {
jump: true
};
rabbit.__proto__ = animal;
console.log(rabbit.eat); // true, taken from the prototype animal
console.log(rabbit.jump); // true
If animal
has any useful method, then since it is a prototype to the rabbit
it is able to call it directly as well!
let animal = {
eats: true,
walk() {
console.log("Walking");
}
};
let rabbit = {
jumps: true,
__proto__: animal // Another way of setting the prototype
};
// walk is taken from the prototype
rabbit.walk(); // Walking
Limitation
The only rules on assigning prototype is that there should be no circular references, and the value of __proto__
should be either an object or null
all other types are simply ignored and have no effect.
Modifying prototype state
What if we did something like this?
let user = {
name: "Ricky"
last: "Lu"
get fullName() {
return this.name + " " + this.last;
}
set fullName(value) {
[this.name, this.last] = value.split(" ");
}
};
let admin = {
__proto__: user,
isAdmin: true
};
console.log(admin.fullName); // Ricky Lu, this works as expected
admin.fullName = "Another person"; // Now what happens to user.name and user.last?
admin.fullName // Another perons
user.fullName // Ricky Lu, it stays! The state of user is protected
As you can see if you tried to modify the admin's name by calling the prototype's setter function the state change will be enforced on admin
and not user
! Even though you are calling user
's setter method. This is because this
always binds to the object that it is called on, and in this case, it is assigning admin.name
and admin.last
to be "Another"
and "person"
respectively. user
is unchanged.
Even if you do something like this:
let user = {
name: "Ricky"
last: "Lu"
};
let admin = {};
admin.name = "Another";
admin.last = "person";
user -> Will still be "Ricky Lu"
admin -> Will be "Another person", because it is assigning name and last property to admin
This behavior is needed because you wouldn't want to modify the prototype's state if multiple object has it as it's prototype, the changes will be untraceable and should be on the object itself not prototype!
for...in loop
Using for...in
it will iterate over the inherited properties as well if it is enumerable, however, Object.keys(obj)
will only return the keys this object has itself, not inherited ones:
let animal = {
eats: true
};
let rabbit = {
jumps: true,
__proto__: animal
};
// Object.keys only returns own keys
console.log(Object.keys(rabbit)); // jumps
// for..in loops over both own and inherited keys
for(let prop in rabbit) console.log(prop); // jumps, then eats
Object.prototype
This is the default prototype that any object created inherits from. And itself's prototype is just null
because there is no one above it.
This object has some default method that all object contains toString, hasOwnProperty,...
hasOwnProperty(prop)
Return true/false
depending on whether or not the object has that particular property itself, and not inherited.
Interesting aside
If you set a property to an object and marked it as enumerable: false
, and you console.log
the object, that property will not show up because it is not iterated over using for...in
loop!