Just JavaScript - Life Without Type - Constructor And InstanceOf
Written by Ian Elliot   
Thursday, 05 March 2015
Article Index
Just JavaScript - Life Without Type - Constructor And InstanceOf
Finding the Constructor
Subtype

The Constructor As An Objects "type"

Every JavaScript object is associated with two important objects - its constructor and its prototype and both play a role in attempts to apply the ideas of type to JavaScript.

The constructor is the first related object we need to consider because a constructor usually creates multiple identical objects. In this sense a constructor creates multiple instances of the abstract object it was designed to create.

In fact this cosy picture of a constructor stamping out identical objects isn't necessarily true - but let's assume it is for the moment.

Even object literals can be considered to have Object as their constructor and Object.prototype as its prototype.

If you know an object's constructor then you know the minimum set of properties and methods an object supports i.e. all of those added by the constructor and all of those added by the prototype chain set by the constructor.

  • That is the constructor C of object o determines all of the initial own properties that o has and its prototype chain.  

So there is a very strong sense that the "type" of an object is related to its constructor.

If you know that object o was constructed by C then you can be reasonably sure that C has all of the properties that any other object constructed by C has. 

Even if you allow dynamic ad-hoc properties you can still assume that objects created by C have the same minimum set of properties. It is only when you start using the delete operator that things go wrong. 

However even if you do ban the delete operator this isn't absolutely true because a constructor could conditionally add properties. 

For example:

var C=function(){
   this.x=10;
   if(Math.random()<0.5){
     this.y=20;
   }
}

This creates an object which always has a property x but only has a property y 50% of the time. So if you discover that obj has been created by C you can, delete not withstanding, safely assume that it has a property x but not that it has a property y.

Such constructors are perverse but they are still valid JavaScript. 

At the end of the day knowing an object's constructor is still your best guide to what properties it should have but it is far from perfect. 

As long as you make the rule that a constructor always creates objects with the same set of properties then it seems reasonable to use the name of the constructor as if it was the object's type. 

So, borrowing from class-based language jargon, we can say that

  • an object o constructed by C is an instance of C

This isn't good jargon because o and C are two very different objects and to say o is an instance of C suggests that it is somehow similar but it is jargon you will find commonly used.  

Finding The Constructor 

So how can you discover and object's constructor?

At first it seems easy - every object has a constructor property which is set to reference its constructor function - but it isn't at this easy and it often causes lots confusion. 

There is a big problem with the constructor property in that it only references the correct constructor in a small number of situations. 

What is worse is that its behavior seems to be complicated and difficult to understand.

The simplest way to follow what is happening is to discover how the constructor property is set.

When you create a function object, any function object not just a constructor the system automatically creates a prototype property and sets this to reference new empty object.

That is what the system does is equivalent to:

var C=function(){
   ...
  };
C.prototype={};

Notice that the default prototype object is created when the constructor function is defined not when it is called.

This is important because this means that the function object is associated with just one prototype object which, unless you change it, it is the same at every call. 

As well as creating a default prototype object the system also creates a constructor property and sets it equal to the function. That is what the system does is almost equivalent to:

var C=function(){
   ...
  };
C.prototype={};
C.prototype.constructor=C;

The only subtle point that we can mostly ignore is that the constructor property is actually set on the prototype's prototype so it doesn't show as an own property.  Don't worry about this for the moment all that really matters is that you understand that the constructor property is created and initialized just once when the function is defined - and not each time it is called. 

This mechanism works just fine in simple situations.

For example:

var A=function(){
 this.z=20;
};

This function definition has resulted in a prototype property being created, referencing a new empty object, with a constructor property referencing the function object A.

You can see that this is true by accessing A.prototype.constructor, e.g.:

console.log(A.prototype.constructor);

which displays the definition of function A. 

In this case you can create an instance of A and check to see what its constructor is:

var a=new A();
console.log(a.constructor);
console.log(a.constructor===A);

You will again see the definition of function A which is indeed the constructor or object a i.e the final instruction is true.

So in this simple case the prototype object that a constructor sets supplies the object constructed with a reference to its constructor - which is exactly what we need.  

So to test if the object referenced by a has been constructed by the constructor referenced by A you would use:

if(a.constructor===A){

The condition is true if constructor and A reference the same Function object. Which is true in this case. 

The constructor property correctly identifies the constructor function in this case - but it is the only case where it does. 

Now consider setting your own custom prototype object. 

var B=function(){
 this.y=20;
};
B.prototype=new A();

The assignment of to prototype replaces the default prototype object created by the system. What happens is almost equivalent to:

var B=function(){
   ...
 };
B.prototype={};
B.prototype.constructor=B;
B.prototype=new A();

You will notice that we have now lost the setting of the prototype's constructor property to B because we have replace the default prototype object.

The new prototype object created using A does have a constructor property but it is set to reference function A and not the correct constructor B. 

So now if you try

var b=new B(); 
console.log(b.constructor===B);

you will find that the result is false. You will also find that

console.log(b.constructor===A);

is true. 

Because the constructor property is only set for the default prototype object it doesn't get updated correctly when you override this with some other prototype object. 

As a result in any prototype chain the constructor property is only set correctly for the first object in the prototype chain and then only if it is the default prototype object created by the constructor. 

This is the reason why it is often said that the constructor property gives the constructor, not of the object, but of the object's prototype. 

This is certainly true in this case but it is only because the prototype chain is just two objects long (ignoring Object and null). 

If you try:

var C=function(){
  this.x=30;
 };
C.prototype=new B();

var c=new C();

console.log(c.constructor===C); console.log(c.constructor===B); console.log(c.constructor===A);

You will discover that the constructor property is set to A and not B as it should be if it was the constructor of C's immediate prototype and not C if it was the constructor of c. 

It is set to A - the constructor of the first object in the prototype chain. 

You can fix this by explicitly setting the constructor property each time you supply your own custom prototype object:

var A=function(){
 this.z=20;
};
var a=new A();
console.log(a.constructor===A);

var B=function(){
 this.y=20;
};
B.prototype=new A();
B.prototype.constructor=B;
var b=new B();
console.log(b.constructor===B);

var C=function(){
  this.x=30;
};
C.prototype=new B();
C.prototype.constructor=C;
var c=new C();
console.log(c.constructor===C);

 

You will now find that each object does have a constructor property, supplied by its prototype, that does give the correct constructor. 

So problem solved - as long as you remember to do the book keeping because JavaScript doesn't do it for you except in the case of a default prototype object.  

To be precise:

  • If you want to make use of the constructor property to track the "type" of an object you have to remember to set it yourself.



Last Updated ( Sunday, 10 May 2015 )