Javascript inheritance implementation using prototypes

This is a fully functional implementation with no weird back references or unwanted functionality. I’ve gone through many different implementations before settling on this. Keep in mind that I had some constraints you may not have and could not use the new ECMA 2015 syntax that introduced the “class” keyword among many other great enhancements.

The Javascript world is full of surprises and tricks that could leave your head spinning for days especially if you are coming from a world of structure and order such as Java or C#.

Object inheritance is complicated and messy, there are several ways of implementing it and different flavor for each creating a huge web of complicated code to choose from.

There are new developments and methods that will definitely improve this situation but if you are forced to stay within the bounds of older implementations like I was while implementing Jurassic interpreter within C# code, the following will get the job done.

Requirements

These are my requirements:

  1. Parent “class” with properties and methods and a parameterless constructor.
  2. Child “class” with own properties and methods and a parameterless constructor.
  3. Child class will inherit Parent’s properties
  4. Child class will inherit Parent’s methods
  5. You can add methods to either Parent or Child even after objects were created.
  6. Adding method to parent will also make that same method available to all children automatically
  7. There are no unwanted references maintained between the Parent and Child definitions or any of the object instantiated using them. (This is often an issue with some of the other ways you can attempt inheritance in javascript)

Implementation

We’ll start by defining constructors that will describe the objects. You will notice Object.assign(this, new Grandparent()); call, that call creates an instance of the base object similar to a super or base call in other languages. I’ve tried using the “call” function Grandparent.call(); but couldn’t get it working in my environment.

var Grandparent = function() {
	this.name = 'I am the grandfather';	
	this.age = 70;
	this.likesCoffee = true,
	this.items = {
		house: {
			bedrooms: 7
		}
	},
	this.getItems = function() {
		return this.items;
	}
}

var Parent = function() {
	Object.assign(this, new Grandparent()); // Copy properties from Granparent, Requirement #3
	this.name = 'I am the parent.';
	this.age = 40;
	this.secretLanguage = function() {
		return 'the secret language';
	}
}

var Child = function() {
	Object.assign(this, new Parent()); // Copy properties from Parent, Requirement #3
	this.name = 'I am the child.';
	this.age = 10;
}

The next step is to link the prototypes so that any new functions added to the base class will propagate down the inheritance hierarchy. Note that by linking Parent to Grandparent and Child to Parent the child will also be linked to Grandparent by default.

Parent.prototype = Object.create(Grandparent.prototype);
Child.prototype = Object.create(Parent.prototype);

To illustrate that we can add a new prototype method to the Grandparent that will then be available to both Parent and Child

Grandparent.prototype.getAge = function() {
	return this.age + ' years';
}

Full working example

Below is a complete example with comments. It addresses all of the requirements stated before.

//
// Define the "constructors", this is somewhat similar to defining a class
// Requirements #1 and #2

var Grandparent = function() {
	this.name = 'I am the grandfather';	
	this.age = 70;
	this.likesCoffee = true,
	this.items = {
		house: {
			bedrooms: 7
		}
	},
	this.getItems = function() {
		return this.items;
	}
}

var Parent = function() {
	Object.assign(this, new Grandparent()); // Copy properties from Granparent, Requirement #3
	this.name = 'I am the parent.';
	this.age = 40;
	this.secretLanguage = function() {
		return 'the secret language';
	}
}

// Link the prototype
// This will give access to current and future methods of the Grandparent

Parent.prototype = Object.create(Grandparent.prototype);

var Child = function() {
	Object.assign(this, new Parent()); // Copy properties from Parent, Requirement #3
	this.name = 'I am the child.';
	this.age = 10;
}

// Link the prototype
// This will give access to current and future methods of the Parent and Grandparent

Child.prototype = Object.create(Parent.prototype);

// Now we are ready to create some people

var grandpa = new Grandparent();
var dad = new Parent();
var son = new Child();

// Both dad and son should like coffee 
// Requiement #3

console.log("Dad likes coffee: ", dad.likesCoffee);
console.log("Son likes coffee: ", son.likesCoffee);

// Both dad and child have their own secretLanguage method
// but not the grandparent
// Requirement #4

console.log("Dad knows ", dad.secretLanguage());
console.log("Son knows ", son.secretLanguage());
try {
	console.log("Grandpa knows ", grandpa.secretLanguage()); // this will fail
} catch(e) {
	console.log(e);
}

// Now lets try to add a method to the Grandparent
// Requirement #5

Grandparent.prototype.getAge = function() {
	return this.age + ' years';
}

// Check if the method is in fact available at the different levels.
// Requirement #6

console.log("Grandpa's age: ", grandpa.getAge());
console.log("Dad's age: ", dad.getAge());
console.log("Son's age: ", son.getAge()); 

// Now how about a method that was defined on Grandparent before
// we declared Parent and Child
// The grandparent has a large house so they all live together

console.log("Son's house: ", son.getItems().house);

// Lets say that the dad decides to move out along with his son
// into a new smaller home. (One would say it's about time)
// We could update the house property to an entirely new
// object like so:
/*
	dad.items.house = {
		bedrooms: 3
	};
*/
// But since there should be no "back" references we can also
// just update the number of bedroom directly

dad.items.house.bedrooms = 3;
console.log("Dad's house: ", dad.getItems().house);

// This should not affect grandpa's living situation
// Requirement #7

console.log("Grandpa's house: ", grandpa.getItems().house);

// But we do want to keep son's house in sync with the dad from now on
// so let's create a reference

son.items.house = dad.items.house
console.log("Son's house: ", son.getItems().house);

Output

The script should produce the following output:

Dad likes coffee: true
Son likes coffee: true
Dad knows the secret language
Son knows the secret language
TypeError: grandpa.secretLanguage is not a function
at :60:40
Grandpa’s age: 70 years
Dad’s age: 40 years
Son’s age: 10 years
Son’s house: {bedrooms: 7}
Dad’s house: {bedrooms: 3}
Grandpa’s house: {bedrooms: 7}
Son’s house: {bedrooms: 3}

Leave a Reply

Your email address will not be published. Required fields are marked *