links: YDK JS Chapter 3
Prototype
Javascript is a prototype based language. prototype based programming is a style of object oriented programming in which behavior reuse(known as inheritance) is performed via a process of reusing existing objects that serve as prototypes. This model can also be known as prototypal prototype-oriented, classless, or instance based programming language
Prototype based programming uses generalized objects, which can then be cloned and extended. Using fruit as an example, a “fruit” object would represent the properties and functionality in general. A “banana” object would be cloned from the “fruit” and general properties specific to bananas would be appended. Each individual “banana” object would be cloned from the “banana” object
Prototypal Inheritance
In programming we often want to take something and extend it.
For instance, we have a user object with it’s properties and methods, and want to make admin and guest as slightly modified variants of it. We would like to reuse what we have in user not copy/re-implement it’s methods, just build a new object on top of it.
Prototypal inheritance is a language feature that helps in that.
Definition of Prototype
In JS, we have a special hidden property Prototype (as named in the specification). that is either null or references to another object. That object is called “a prototype”
When we read a property from an object, and it’s missing, JS automatically takes it from the prototype. In programming such thing is called prototypal inheritance
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 = {
eats: true
}
let rabbit = {
jumps: true
}
rabbit.__proto__ = animal // Sets rabbit.[[Prototype]] = animal
Now if we read a property from rabbit and it’s missing. JS will automatically take it from animal
For instance:
let animal = {
eats: true
};
let rabbit = {
jumps: true
};
rabbit.__proto__ = animal; // (*)
// we can find both properties in rabbit now:
alert( rabbit.eats ); // true (**)
alert( rabbit.jumps ); // true
Here we can say animal is the prototype of rabbit or rabbit prototypically inherits from animal
So if animal has a lot of useful properties and methods, then they become automatically available in rabbit. Such properties are called “inherited”.
If we have a method in animal, it can be called on rabbit:
let animal = {
eats: true,
walk() {
console.log('animal walk')
}
}
let rabbit = {
jumps: true,
__proto__: animal
}
// walk is taken from the prototype
rabbit.walk() // animal walk
There are two limitations on prototypes:
- The references can’t go in circle, JS will throw an error, if we try to assign
__proto__in circle. - The value of
__proto__can either benullor object, 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.
__proto__is a historical getter/setter from Prototype
It’s a common mistake of novice developers not to know the difference between these two.
Please note the __proto__ is not same as internal Prototype property. It’s a getter/setter for Prototype
The __proto__ property is a bit outdated and exists only for historical reasons, modern JS suggests that we should use Object.getPrototypeOf/Object.setProtptypeOf functions instead that get/set the prototype
By the specification, __proto__ must only be supported by browsers. In fact though, all environments including server-side support __proto__, so we’re quite safe using it.
Writing doesn’t use prototype
The prototype is only used for reading properties.
Write/delete operations work directly with the object.
In the example below, we assign its own walk method to rabbit:
let animal = {
eats: true,
walk() {
/* this method won't be used by rabbit */
}
}
let rabbit = {
__proto__: animal
}
rabbit.walk = function() {
console.log('Rabbit, Bounce Bounce')
}
rabbit.walk() // Rabbit, Bounce Bounce
From now on, rabbit.walk() call finds the method immediately in the object and executes it, without using the prototype:
❗️Accessor properties are an exception
Accessor properties are an exception, as assignment is handled by a setter function. So writing to such property is actually the same thing as calling a function
For that reason admin.fullName works correctly in the code below:
let user = {
name: 'Subramanya',
surname: 'Nookala'
set fullName(value) {
[this.name, this.surname] = value.split(' ')
}
get fullName() {
return `${this.name} ${this.surname}`
}
}
let admin = {
__proto__: user,
isAdmin: true
}
console.log(admin.fullName) // Subramanya Nookala
admin.fullName = 'Mamatha Nookala'
console.log(admin.fullName) // Mamatha Nookala, State of admin modified
console.log(user.fullName) // Subramanya Nookala, State of user protected
The Value of this
An interesting question may arise in the example above, what’s the value of this inside set fullName(value)? where are the properties this.name and this.surname written: into user or admin?
The answer is simple this is not affected by prototypes at all.
No matter where the method is found: in an object or it’s prototype. In a method call, this is always the object before the dot
So, the setter call admin.fullName= uses admin as this, not user.
Summary
- In JS all objects have a hidden property Prototpye that’s either object or
null - We can use
obj.__proto__to access it - the object referenced by Prototype is called prototype
- If we want to read a property of obj or call a method, and it doesn’t exist, then JavaScript tries to find it in the prototype.
- Write/delete act directly on the object, they don’t use prototype(assuming it’s a data property not a setter)
- If we call
obj.method(), and the method is taken from the prototype,thisstill referencesobj. So methods always work with the current object even if they are inherited. - The
for..inloop iterates over both its own and its inherited properties. All other key/value-getting methods only operate on the object itself.
tags: javascript , fundamentals