Introduction to JavaScript’s Prototype Inheritance

jslogoIf you have worked in traditional classical object oriented (OO) languages, JavaScript’s (JS) prototypal inheritance model may be a source of confusion. It certainly was for me when I started working in JS.

What is a “Classical Object Oriented model?”
Many people believe that the word “classical” in “classical OO” has something to do with tradition, or a history of OO programming. This is actually incorrect. The word classical in this instance actually refers to the base word “class,” which in OO means a data structure that allows one to create unique instances of objects through classes.  If you are familiar with Java or ActionScript3, you have most likely used classes.

How is “prototypal inheritance” different from “classical inheritance?”
Classes can inherit from other classes by extending them. When one class extends another, the extended class is often referred to as the base, super or parent class. For the purposes of this article, I will refer to them as a parent and child relationships when referring to inheritance. The child class inherits a specified set of methods and properties from the parent class.

Prototypal inheritance is based off an an actual instance of an object – not the construct to create that instance. So when you create your parent object with all its specific properties and methods, the child inheriting from the parent gets all the original objects values. They’re live and will change automatically for the child when its created.

I like to think of classical inheritance as blue-prints. You ‘construct’ things from blueprints, and each one will be unique and different. When a child gets a hold of the original parent’s blue-print, it can add on rooms and make changes, but it won’t affect any of the objects already created with either child blue-prints, or their parents.  It also does not require any parent objects to be created in order to create children.  They can be created directly from their own blueprints.

Prototypal inheritance on the other hand is like building with sheets glass. You create a new huge sheet of glass, and specify all the properties that object will need. Your child then is another sheet of glass put on top of the original. You can keep adding sheets of glass for other children to lay on top of the original. When a change occurs to the original parent sheet of glass though, its automatically seen by all the other child layers of glass on top of it. Thats because our prototype inheritance is live and uses real object instances!  The important thing to note is that the child cannot function without an instance of a parent – without that initial sheet of glass, the other layers of glass break.

Lets see prototypal inheritance in action!

// Create my base person object
var BasePerson = function( ) {
     this.sex = 'male';
     this.occupation = 'worker';
     this.name = 'John Doe';
}

var myBasePerson = new BasePerson();
BasePerson.prototype.whoAmI = function() {
     return 'A ' + this.sex + ' ' + this.occupation
             + " named " + this.name + '. \n';
}

console.log('myBasePerson: ' + myBasePerson.whoAmI() );
// basePerson: A male work named John Doe.

// Lets create our base artist object to inherit from
var BaseArtist = function() {
     this.media = 'mixed';
     this.artStyle = 'various';
     this.occupation = 'artist';
}
BaseArtist.prototype = myBasePerson;
BaseArtist.constructor = BaseArtist;
var myBaseArtist = new BaseArtist();
myBaseArtist.occupation = 'artist';

/* TIP: If you completely replace a prototype, its good
practice to reset the constructor back to the
the object you are defining - as I did above */
var dcholth = new BaseArtist();
dcholth.name = 'D.C. Holth';
console.log( dcholth.whoAmI() );
// A male artist named D.C. Holth.

console.log( myBasePerson.whoAmI() );
// A male worker named John Doe.

/* Note that both my objects are still able to use whoAmI()
and that they both still access this.occupation, even though
they are not the same .occupation in memory anymore. The 'this'
declaration points to our new instance, not the prototype or
global space */

// Painters are another subsection of artists.
var BasePainter = function() {
     this.paintingStyle = 'Traditional';
}
BasePainter.prototype = myBaseArtist;
BasePainter.constroctor = BasePainter;
// You can also attach properties to the prototype
// by putting them directly onto the property in
// literal notation
BasePainter.prototype.media = 'paint';

// Now lets create some instances of our painters
var monet = new BasePainter();
monet.artStyle = 'impressionistic';
monet.name = 'Monet';

var maria = new BasePainter();
maria.artStyle = 'abstract';
maria.name = 'Maria Cosway';
var dali = new BasePainter();
dali.artStyle = 'surrealist';
dali.name = 'Dali';
// Sending some information to our console we can see that
// our prototype and its children are working correctly.
// We have 'artists' with their own unique 'art style'.

console.log('Monet's occupation: ' + monet.occupation );
//Monet's occupation: artist

console.log('Monet's style: ' + monet.artStyle + '\n' );
//Monet's style: impressionistic

console.log('Maria Cosway's occupation: ' + maria.occupation );
//Maria Cosway's occupation: artist
console.log('Maria Cosway's style: ' + maria.artStyle + '\n');
//Maria Cosway's style: abstract

console.log('Dali's occupation: ' + dali.occupation );
//Dali's occupation: artist
console.log('Dali's style: ' + dali.artStyle + '\n' );
//Dali's style: surrealist

// When we ask our function to execute whoAmI() it is still
// using our original whoAmI function from myBasePerson
console.log( dali.whoAmI() );
// A male artist named Dali.

// Lets update so that all our artists get access to a more
// specific whoAmI dealing just to artists.
BasePainter.prototype.whoAmI = function() {
     return this.name + ' is a ' + this.sex + ' ' + this.occupation
     + ' who uses ' + this.media + ' to make '
     + this.artStyle + ' artwork \n';
}
console.log( dali.whoAmI() );
// Dali is a male artist who uses paint to make surrealist artwork

console.log( maria.whoAmI() );
// Maria Cosway is a male artist who uses paint to make abstract artwork
// Wait a second? Maria isn't a MALE artist!

// now I'm sure you know by now that you could simply
// do maria.sex = 'female' and all only she will become
// female. But lets have a little fun here.

// By changing the prototype from which BasePainter's inherit,
// all the way down to myBasePerson, we can have
// EVERYONE be female automatically!
myBasePerson.sex = 'female'

console.log( dali.whoAmI() );
// Dali is a female artist who uses paint to make surrealist artwork

console.log( maria.whoAmI() );
// Maria Cosway is a female artist who uses paint to make abstract artwork

console.log( myBasePerson.whoAmI() );
// A female worker named John Doe.

// Also note again, our whoAmI() function didn't change
// for our myBasePeron object

/* Suddenly we have changed the sex of ALL our people!
This is because of that 'glass analogy' I used earlier.
When the underlying parent glass is changed, it shines
through to all our objects! So when we changed the sex
of our very early prototype object 'myBasePerson' all
the children that inherited from it also became female! */

// lets go ahead and switch everyone back to male, and just
// make Maria female
myBasePerson.sex = 'male'
maria.sex = 'female';
console.log( dali.whoAmI() );
// Dali is a male artist who uses paint to make surrealist artwork

console.log( maria.whoAmI() );
// Maria Cosway is a female artist who uses paint to make abstract artwork

console.log( myBasePerson.whoAmI() );
// A male work named John Doe.

You may have noticed that I never used the keyword ‘new’ in the above code block.  I have mixed feelings on using ‘new’ to stamp out objects.  In one respect, I view it as a hold-over to make JavaScript appear to be more classical in its nature.  The fact is, its not a classical language, and trying to mold it into something its not can confuse the mental process needed to understand its prototypal nature.   Using ‘new’ isn’t bad in the right instances, but in my opinion one should find alternatives when ever possible.

Hopefully through the examples above you can see how prototype-inheritance can help  you organize your code and create more efficient prototype-inheritance objects!