JavaScript Inheritance Revisited

JavaScript is an excellent language for Object Oriented programming although its prototypical inheritance mechanism is often misunderstood. Alex Sexton (@slexaxton) recently posted a nice (and somewhat amusing)blog post called "Understanding JavaScript Inheritance" which gives a fresh and interesting view on the whole topic. There are numerous other articles, blog posts, etc., available around this topic. Most of them explain various ways to implement JavaScript inheritance (e.g. this and the famous one by John Resig here). The problem, as far as I can see it, is that as a newbie JavaScript developer one might be lost in the slew of the specific details that each and every such post presents. In this blog post I want to try a different approach where I create a general overview of the various methodologies without naming them with specific names (such as "Functional", "Pseudoclassical", "Pseudoclassifunctional", or "Jerry"). I hope that after reading this blog post you will be able to understand the best direction to take with your inheritance scheme.

 

Creating an object

So JavaScript doesn't have classes, yes no classes at all, well... at least this is true for ES 5, for ES 6 we still don't know. Each object you create has a parent object, this parent is just a regular object as any other object in the language. You can access this parent object through the __proto__ property but don't do this just yet. It is not recommended to use this property and its existence is questionable for ES6. Let's try using the __proto__property in an example (again, this is a bad practice to use this property, I am just making a point here). On a side note I must add that you can access the parent using the Object.getPrototypeOf() function but both ways don't work on IE10 or below.

var parent = {};
var child = Object.create(parent);
console.log(child.__proto__ === parent); // true
console.log(child.__proto__ === {}); // false

I use the Object.create(<object>) function to create a new object out of the parent object. So, what does it mean to be a parent object to some other object? It means you are in the call chain for any function or property on the child object. This is similar to virtual functions in languages that support virtual functions. When I call a function on the child object it looks for the function on the object itself and if it cannot find one it looks on the parent object and so on. Here is an example:

var parent = {};
var child = Object.create(parent);
var grandson = Object.create(child);

parent.foo = function(){ console.log('parent - foo!')}
child.boo = function() { console.log('child - boo!')}
grandson.moo = function() { console.log('grandsone - moo!')}

grandson.foo(); //parent - foo!
grandson.boo(); //child - boo!
grandson.moo(); //grandsone - moo!

child.foo = function() { console.log('child - foo!')}

grandson.foo(); //child - foo!

Everything is just as we expect it to be and obviously the same logic applies for object properties. The object that is pointed by the __proto__ property is sometimes referred to as "the prototype" of our object. I could end the blog post right here but unfortunately JavaScript adds something confusing to the mix.

 

Prototype and Constructor

JavaScript has a special object called Function. When you write the keyword function in your code, an instance of such an object is created (there is another way to create a function, but more on that later). Each instance of a function object has a special property called prototype. This is the confusing part because this is not the prototype of the function object instance, the prototype of the function object instance is pointed by__proto__, remember?. The prototype is the object that will be the parent of any other object instance created by the function when it is used as a constructor. Wait, what? Constructor? Yes, in JavaScript anyfunction can be used as a constructor to create a new object instance. Remember that in the previous paragraph I wrote that each object should have the __proto__ property pointing to the prototype of that object. When you use a function as a constructor it will point the __proto__ property of the newly constructed object to its prototype. To use a function as a constructor you can use the new keyword with the function call.

Here is an example:

var myConstructor = function(){}

var someObject = new myConstructor();

console.log(someObject.__proto__ === myConstructor.prototype); //true

So basically, one could say that the function instance is sort of like a class and like constructor at the same time. We can define all the inherited functions and properties on the prototype and each object created by the function will have those functions and properties through the call chain. Therefore we can do something like this:

var myConstructor = function(){}
myConstructor.prototype.name = "John";
myConstructor.prototype.printName = function(){
    console.log(this.name);
}

var someObject = new myConstructor();
var anotherObject = new myConstructor();

console.log(someObject.name); //John
console.log(anotherObject.name); //John
anotherObject.name = "Smith";
someObject.printName(); //John
anotherObject.printName(); //Smith

We can extend this technique to a full chain of inheritance by utilizing the fact that the __proto__ property is going to point to our prototype. For example, we can add a second level of inheritance like this:

var myConstructor = function(){}
myConstructor.prototype.name = "John";
myConstructor.prototype.printName = function(){
    console.log(this.name);
}

var mySubConstructor = function(){}
mySubConstructor.prototype = new myConstructor();

var someObject = new mySubConstructor();

someObject.printName(); //John

console.log(someObject instanceof mySubConstructor); //true
console.log(someObject instanceof myConstructor); //true

As you can see from the example, the instanceof semantics remains true as in other languages. The next thing we want to add is the super / base semantics which calls the functions and properties from the prototype object. Remember that we don't want to use the __proto__ property so we do a small trick. We can get the function that constructed our object using the constructor property (this works on any object unless someone has changed the value). Now every time we want to call a function on the parent explicitly we can do:

  this.constructor.prototype.functionName();

Since this syntax is a bit tedious we introduce a $super property to the root of all our inheritance chains. The root is the prototype of the Object function. We link the getter of the new property to the relevant code and get:

Object.defineProperty(Object.prototype, "$super", {
    get : function(){ return this.constructor.prototype},
    enumerable : true,
    configurable : true
});

var myConstructor = function(){}
myConstructor.prototype.name = "John";
myConstructor.prototype.printName = function(){
    console.log(this.name);
}

var mySubConstructor = function(){}
mySubConstructor.prototype = new myConstructor();

var someObject = new mySubConstructor();

someObject.printName = function(){
    console.log("Hi, My name is - ");
    this.$super.printName();
}

someObject.printName(); //Hi, My name is - \n John

 

Here you can read the second part of this post which discusses how other frameworks and compiled-to-js languages are doing inheritence 

 

This post has been written by Boris Kozorovitzky

Labels: web
Comments
eitan_peer | ‎05-28-2013 03:14 AM

Insightful post with helpful examples.

Thanks!

Kobrigo | ‎05-29-2013 02:08 AM

Great post.

Carter Chen | ‎06-02-2013 07:19 PM

Simple and easy to follow up, very good article on Javascript Inheritance

Mathew Porter | ‎06-05-2013 11:42 AM

Nice post, there always seems to be a little confusion with people not quite understanding how the inheritance works in javascript.

Elad Moshe | ‎06-12-2013 01:31 PM

Great post, thanks!

RulyWeisbach | ‎06-13-2013 01:05 AM

Great post, few questions

 

I can also define class variables and methods without prototype by defining them in the scope of the function:

 

var myConstructor = function()
{	
	this.name = "John"
	
	this.printName = function()
	{
		console.log(this.name);
	}	
}

 

 

Is there any difference or its the same as using prototype? One advantage of this way is to have private variables if I do it like this:

 

var myConstructor = function()
{

	var name = "John";
	
	this.getName = function()
	{
		return name;
	}
	
	this.printName = function()
	{
		console.log(this.name);
	}	
}

 

 

BorisKozo | ‎06-13-2013 12:48 PM

Hi Ruly,

 

You are right, it is possible to define the variables within the constructor but there are several disadvantages you should be aware of (which are also the reason why this method is rarely used)

 

1) In your first example since you define the "name" variable directly on the newly created instance there isn't one variable which all instances look at by default. If you would define "name" on the prototype instead then each instance can either override the property or use the shared one. 

 

2) In your second example the local variable "name" is not exactly private because it exists only within the scope of the constructor function. This limits the use of this variable to functions defined within the same scope. This means that any function defined later on the instance will not be able to use these scope variables. This also means that if you use "apply" / "call" with this instance you will not have access to the scope variables.

 

3) Perhaps the most important reason is that when defining properties and functions within the constructor function a new instance of each property and function is created for each new instance generated by the constructor function. In your first example, each call to "new myConstructor()" would generate a new (identical) instance of the printName function which consumes a lot of memory. There is an interesting discussion about this in the TypeScript forum, especially check the answer by Anders (the creator of TypeScript) : https://typescript.codeplex.com/discussions/397651

 

Hope this answers your question,

Boris

 

Leave a Comment

We encourage you to share your comments on this post. Comments are moderated and will be reviewed
and posted as promptly as possible during regular business hours

To ensure your comment is published, be sure to follow the Community Guidelines.

Be sure to enter a unique name. You can't reuse a name that's already in use.
Be sure to enter a unique email address. You can't reuse an email address that's already in use.
Type the characters you see in the picture above.Type the words you hear.
Search
Showing results for 
Search instead for 
Do you mean 
About the Author
Featured


Follow Us
The opinions expressed above are the personal opinions of the authors, not of HP. By using this site, you accept the Terms of Use and Rules of Participation.