[[Prototype]]
In JavaScript, objects have a special hidden property [[Prototype]]
(as named in the specification), that is either null or references another object. That object is called “a prototype”:
The property [[Prototype]]
is internal and hidden, but there are many ways to set it.
One of them is to use the special name __proto__
, like this:
let animal = {
walks: true
}
let rabbit = {
jumps: true
}
rabbit__proto__ = animal
When we read a property from object, and it’s missing, JavaScript automatically takes it from the prototype. In programming, this is called prototypal inheritance
.
The prototype chain can be longer as well
let animal = {
eats: true,
walk() {
alert("Animal walk");
}
};
let rabbit = {
jumps: true,
__proto__: animal
};
let longEar = {
earLength: 10,
__proto__: rabbit
};
// walk is taken from the prototype chain
longEar.walk(); // Animal walk
alert(longEar.jumps); // true (from rabbit)
Limitation
- The references can’t go in circles. JavaScript will throw an error if we try to assign proto in a circle.
- The value of proto can be either an object or null. Other types are ignored.
Also it may be obvious, but still: there can be only one [[Prototype]]. An object may not inherit from two others.
Writing doesn’t use prototype
The prototype is only used for reading properties.
Write/delete operations work directly with the object.
let user = {
name: "John",
surname: "Smith",
set fullName(value) {
[this.name, this.surname] = value.split(" ");
},
get fullName() {
return `${this.name} ${this.surname}`;
}
};
let admin = {
__proto__: user,
isAdmin: true
};
alert(admin.fullName); // John Smith (*)
// setter triggers!
admin.fullName = "Alice Cooper"; // (**)
alert(admin.fullName); // Alice Cooper, state of admin modified
alert(user.fullName); // John Smith, state of user protected
The value of “this”
this
is not affected by prototypes at all.
No matter where the method is found: in an object or its prototype. In a method call, this is always the object before the dot.
That is actually a super-important thing, because we may have a big object with many methods, and have objects that inherit from it. And when the inheriting objects run the inherited methods, they will modify only their own states, not the state of the big object.
// animal has methods
let animal = {
walk() {
if (!this.isSleeping) {
alert(`I walk`);
}
},
sleep() {
this.isSleeping = true;
}
};
let rabbit = {
name: "White Rabbit",
__proto__: animal
};
// modifies rabbit.isSleeping
rabbit.sleep();
alert(rabbit.isSleeping); // true
alert(animal.isSleeping); // undefined (no such property in the prototype)
for…in loop
The for..in loop iterates over inherited properties too.
For example:
let animal = {
eats: true
};
let rabbit = {
jumps: true,
__proto__: animal
};
// Object.keys only returns own keys
alert(Object.keys(rabbit)); // jumps
// for..in loops over both own and inherited keys
for(let prop in rabbit) alert(prop); // jumps, then eats
If you don't want to access inherited properties use obj.hasOwnProperty(key)
. It returns true
if obj has its own (not inherited) property named key.
let animal = {
eats: true
};
let rabbit = {
jumps: true,
__proto__: animal
};
for(let prop in rabbit) {
let isOwn = rabbit.hasOwnProperty(prop);
if (isOwn) {
alert(`Our: ${prop}`); // Our: jumps
} else {
alert(`Inherited: ${prop}`); // Inherited: eats
}
}
F.prototype(function prototype)
Please note that F.prototype
here means a regular property named "prototype" on F
. It sounds something similar to the term “prototype”, but here we really mean a regular property with this name.
let animal = {
eats: true
};
function Rabbit(name) {
this.name = name;
}
Rabbit.prototype = animal;
let rabbit = new Rabbit("White Rabbit"); // rabbit.__proto__ == animal
alert( rabbit.eats ); // true
Setting Rabbit.prototype = animal
literally states the following: "When a new Rabbit is created, assign its [[Prototype]]
to animal".
F.prototype
only used at new F
time
F.prototype property is only used when new F is called, it assigns [[Prototype]] of the new object.
If, after the creation, F.prototype property changes (F.prototype = "another object"), then new objects created by new F will have another object as [[Prototype]], but already existing objects keep the old one.
Default F.prototype, constructor property
Every function has the "prototype" property even if we don’t supply it.
The default "prototype" is an object with the only property constructor that points back to the function itself.
Like this:
function Rabbit(){
// some code
}
console.log(Rabbit.prototype.constructor === Rabbit) // true
Native prototypes
Object.prototype
let obj = {};
alert( obj ); // "[object Object]" ?
Where’s the code that generates the string "[object Object]"? That’s a built-in toString method, but where is it? The obj is empty!
…But the short notation obj = {} is the same as obj = new Object(), where Object is a built-in object constructor function, with its own prototype referencing a huge object with toString and other methods.