&
Prototypal InheritanceAn Object is a collection of key/value pairs,
referred to as the Object's properties:
let obj = {
key: "value",
first_name: "kostas",
last_name: "minaidis"
}
1) Object Literal:
let obj = { first: "kostas", last: "minaidis" }
2) Object Constructor:
let obj = new Object({ first: "kostas", last: "minaidis" });
Both create the following structure:
Object obj {
first: "kostas",
last: "minaidis",
__proto__: Object
}
Whenever we create a new Object, a __proto__ property is implicitly attached to it.
Object obj {
first: "kostas",
last: "minaidis",
__proto__: Object
}
The __proto__ property is a link that points to: Object.prototype
obj.__proto__ === Object.prototype
The Object.prototype is also an object itself and contains a set of helper methods. The role of __proto__ is to implicitly make these methods available to the newly created object.
Object.prototype {
constructor: [Function],
hasOwnProperty: [Function],
toString: [Function],
valueOf: [Function],
...
}
let obj = {
key: "value",
__proto__: Object.prototype
}
Accessing the obj object's own properties:
obj.key;
// "value"
Accessing the prototype's methods:
obj.valueOf();
// { key: "value" }
obj.hasOwnProperty( "key" );
// true
obj.hasOwnProperty( "valueOf" );
// false
{
constructor: [Function],
hasOwnProperty: [Function],
toString: [Function],
valueOf: [Function],
... more methods ...
}
What is a Constructor?
A function called with the new keyword.
A function constructor always returns a new object
whose type depends on the constructor.
let obj = new Object( { key: "value" } );
let arr = new Array( 1, 2, 3 );
let err = new Error( "Whoops!" );
{ key: "value" }
[ 1, 2, 3 ]
Error: Whoops!
All of our newly created objects contain a __proto__ property
which links them to their constructor's prototype object...
obj { key: "value" }
arr [ 1, 2, 3 ]
err Error: Whoops!
obj.__proto__ === Object.prototype
arr.__proto__ === Array.prototype
err.__proto__ === Error.prototype
...and subsequently to the object prototype's inherited methods:
obj.valueOf(); // { key: "value" }
arr.reverse(); // [ 3, 2, 1 ]
err.message; // ""
Object.prototype.valueOf
Array.prototype.reverse()
Error.prototype.message
By linking object instances to their Constructor prototype object we are conserving memory and gaining speed.
Multiple instances of a Constructor, can share a single set of methods through the prototype object, while maintaining their own unique set of property values:
Array.prototype.sort [Function]
Array.prototype.reverse [Function]
Array.prototype.map [Function]
...
// Unique properties: // Shared methods:
let arr1 = [1,2,3]; arr1.sort();
let arr2 = [4,5,6]; arr2.sort();
let arr3 = [7,8,9]; arr3.sort();
We can also create our own Constructors using custom functions and construct custom objects using the new keyword.
function Pizza( sauce, cheese ){
this.sauce = sauce;
this.cheese = cheese;
}
let yummy = new Pizza("tomato","mozzarella");
yummy {
sauce: "tomato",
cheese: "mozzarella"
}
By utilizing JavaScript's prototype object and its underlying linkage mechanism we can implement some powerful object oriented patterns.
Let's dive into some examples...
Create a Constructor that will create new instances with unique properties:
function User( options ) {
this.name = options.name;
this.surname = options.surname;
this.plan = options.plan;
}
Add methods to our Constructor's prototype object.
These methods will be created once but will be shared by all new instances.
User.prototype.getFullName = function(){
return this.name + " " + this.surname;
}
User.prototype.upgradePlan = function(){
this.plan = "Premium";
}
Our newly created instances will be sharing the same set of methods
inherited from their constructor's prototype object:
let user001 = new User(
{ name: "Jane", surname: "Doe", plan: "Basic" }
);
let user002 = new User(
{ name: "John", surname: "Dalton", plan: "Basic" }
);
let user003 = new User(
{ name: "Rebecca", surname: "Brown", plan: "Basic" }
);
user001.getFullName(); // "Jane Doe";
user002.upgradePlan(); // { plan: "Premium", name: "John", ... }
user003.getFullName(); // "Rebecca Brown"
We can extend Constructors and their prototype object
by using the Object.create() builtin method.
let Child = Object.create( Parent );
From MDN: The Object.create() method creates a new object, using an existing object as the prototype of the newly created object.
We can use the power and flexibility of Object.create() to extend an object's prototype like this:
Child.prototype = Object.create( Parent.prototype );
Let's extend our User Object to create a new type of Premium User object
function PremiumUser( options ){
User.call(this, { name: options.name, surname: options.surname });
this.plan = "Premium";
}
PremiumUser.prototype = Object.create( User.prototype );
PremiumUser.prototype.constructor = PremiumUser;
PremiumUser.prototype.special = function(){
return "Premium Specials!";
}
// Shadowing / Overriding our Parent's method:
PremiumUser.prototype.upgradePlan = function(){
this.plan = "Golden";
}
Let's create our first Premium User instace:
let prem001 = new Premium({ name: "Clark", surname: "Kent" })
prem001.getFullName(); // "Clark Kent"
prem001.upgradePlan(); // { plan: "Golden", name: ... }