Understanding constructor and prototype.
We all know Javascript uses a Prototype Chain in order to bring inheritance to the language. In this article we are going to analyze the different keywords constructor
and prototype
that help us to use the Prototype Chain properly.
When, how and what is a ... constructor
A constructor
is a pointer. It points to the Function() that created the point from which you are retrieving the constructor
from. Let's see the following code:
var MarkBlueprints = function StarkIndustries(){}
(We are using named functions in order to show the differences between a simple variable and a function that works as a constructor
. As a good practice, try to name your functions for easy debugging).
If we used StarkIndustries
to create an object (in our case a deadly robot), we can say that the object constructor is StarkIndustries
.
var mark_I = new MarkBlueprints();
mark_I.constructor // returns function StarkIndustries(){}
Since the constructor is just a reference to a Function()
, we can call it as many times as we want.
var mark_II = new (mark_I.constructor)()
You might have seen already that the constructor
property only appears when we use the keyword new
in our functions. Otherwise, we are just calling that function and not really creating and object but storing the result of that object's processing.
Any object created through a new
keyword has a constructor. Example:
new Boolean(true).constructor // returns function Boolean() { [native code] }
[].constructor // returns function Array() { [native code] }
(10).constructor // returns function Number() { [native code] }
(Wait, why (10).constructor works? Because we are surrounding 10 with parenthesis, allowing Javascript to "wrap" the primitive native value "10" into a "complex object" that uses the Number constructor. Try doing 10.constructor
and you will get an error. Javascript automatically returns the primitive value to its native state after you finish treating it as an object, or in other words, it unwraps it)
One of the uses of the constructor is to help you create replicate copies of an object. Since the constructor property is a reference to the function that created the object, as long as you have a copy of the object, it will always point to the original constructor.
var MarkBlueprints = function StarkIndustries(){}
var mark_I = new MarkBlueprints();
mark_I.constructor; // returns function StarkIndustries(){}
var MarkBlueprints = function StaneIndustries(){}
mark_I.constructor; // still returns function StarkIndustries(){}
The constructor is set by object, so changing an object constructor
property will only change that specific object constructor
.
var mark_II = new (mark_I.constructor)();
var Mark2ndGenBlueprints = function StarkEnterprises(){};
mark_II.constructor = Mark2ndGenBlueprints;
var mark_III = new (mark_II.constructor)();
mark_III.constructor // returns function StarkEnterprises(){};
mark_II.constructor = MarkBlueprints; // Piper wants to rollback the model :(
mark_III.constructor // returns function StarkEnterprises(){}, good thing I kept one safe :D
There are some patterns that use the constructor
in order to provide inheritance based patterns as well as to provide factory strategies. Here are some examples:
function Robot() {};
Robot.prototype.fire = function() { console.log("Sending rockets"); };
function IronMan() { Robot.call(this); }; // calling "parent" constructor
IronMan.prototype = new Robot(); // inheritance
IronMan.prototype.constructor = IronMan; // fixing prototype pointer.
var mark_I = new IronMan();
The reason we "fixed" last prototype constructor is because we are expecting developers to use our object constructor in a clean OOP way.
mark_I instanceof IronMan // returns true, perform IronMan operations
mark_I instanceof Robot // returns true, perform Robot operations
We can always rely on the Prototype chain to perform the lookup for methods, but that's a Javascript feature not everyone is familiar with. We used the prototype
before to show this example, so let's describe prototype
now.
When, how and what is a ... prototype
The keyword prototype
is a property of Function() objects. No other type of objects have this property. By now you should realize that whenever you type in your console Object
or Array
you are actually calling the Javascript default functions; give it a try:
typeof String // returns function
typeof Number // returns function
typeof Boolean // returns function
...
(You are actually calling window.String
, window.Number
, window.Boolean
and so on; since we need a place to store to store our native implementation of these functions, in the browser we use the Global/Head object window
)
The value of a prototype
is the object constructor
that created that specific object. That's when things get funky. Let's see a couple prototypes:
Boolean.prototype // returns Object Boolean
String.prototype // returns Object String with methods such as "toUpperCase"
Function.prototype // returns function() {} or function Empty() {}
All native and complex objects retrieve to their original constructors, which in this case are themselves. The only exception is the Function prototype, which returns the Function()
function that created it. Don't confuse it with the constructor, as it's not the same.
Function.prototype === Function.constructor // returns false, Function.constructor is function Function(){}
Most of the time you don't want to be middling with the native prototypes since modifying them will make all the objects that inherit from that prototype be modified as well. This is dangerous, because you don't know which "version" of the prototype you are developing with, the native ones or modified ones. Plus, whenever you do a for ... in object
, all the prototype properties will show up.
var jerichoMissile = { amountOfMissiles: 10 }
Object.prototype.containsShrapnel = true
"containsShrapnel" in jerichoMissile // returns true, although if you debug jerichoMissile it doesn't show it
As a rule of thumb, don't modify the native prototypes unless you want to give missing functionality. For example, in Javascript 1.8.6 a we have the method Object.watch. If we want to bring this functionality to Javascript 1.8.5, we can extend this functionality through a custom method:
if (!Object.prototype.watch) { // we ensure we are not overriden a default property
Object.prototype.watch = function(prop, handler) {
// Custom method here.
}
}
// Full code [here](https://gist.github.com/384583)
(In Javascript 1.8.5 we use the Object.defineProperty
as a cleaner way to extend an object)
What you probably want to do is use prototype
to create nice reusable code for your custom functions. Let's use an example:
var MarkBlueprints = function RobotModels(){}
MarkBlueprints.prototype.getRockets = function(){ return this.rockets; }
var mark_I = new MarkBlueprints();
mark_I.getRockets() // returns undefined, boring.
// Let's add some rockets!
var Mark2ndGenBlueprints = function RocketModels(){ this.rockets = 6; }
// We already coded a way to retrieve rockets, so we update our prototype to use a RobotModel instead
Mark2ndGenBlueprints.prototype = new MarkBlueprints();
var mark_II = new Mark2ndGenBlueprints();
mark_II.getRockets() // returns 6, yeah!
// Let's add some lasers!
var Mark3rdGenBlueprints = function LaserModels() { this.lasers = 2; }
// We don't have yet a way to retrieve lasers, so we add one.
Mark3rdGenBlueprints.prototype.getLasers = function() { return this.lasers; }
Mark3rdGenBlueprints.prototype.totalWeapons = function() { return this.lasers + this.rockets; }
// Shi'em with rockets!
Mark3rdGenBlueprints.prototype = new Mark2ndGenBlueprints();
var mark_III = new Mark3rdGenBlueprints();
mark_III.totalWeapons() // returns TypeError: Object #<RobotModels> has no method 'totalWeapons'
What happened? RobotModels? It's supposed to be LaserModel! Well, remember, the prototype's value is the constructor that created the object. After updating the prototype of the LaserModels()
with RobotModels()
, we also wrote over the constructor, because the constructor's value is the Function()
that created the object! Then, we overwrote our getLasers and totalWeapons, because the RobotModels don't have such things.
// Let's fix the reference.
Mark3rdGenBlueprints.prototype.constructor = Mark3rdGenBlueprints;
// We need to tell again our methods because we overwrote in the prototype last time!
Mark3rdGenBlueprints.prototype.getLasers = function() { return this.lasers; }
Mark3rdGenBlueprints.prototype.totalWeapons = function() { return this.lasers + this.rockets; }
var mark_IV = new Mark3rdGenBlueprints();
mark_IV.totalWeapons() // returns 8, it's on!
There' one final note on prototypes. Prototypes share their properties with their objects; it's a good way to store functions, but properties can get messed up really easy if you describe them in the prototypes. Prototype properties work in a similar fashion than static
variables in classes such as C and Java. Let's see an example:
var IronManBlueprints = function FlyingModel() {}
IronManBlueprints.prototype.missiles = 20;
IronManBlueprints.prototype.fireMissiles = function() {
IronManBlueprints.prototype.missiles--; return IronManBlueprints.prototype.missiles + " missiles left";
}
var mark_V = new IronManBlueprints();
var mark_VI = new IronManBlueprints();
mark_V.fireMissiles() // returns "19 missiles left"
mark_VI.fireMissiles() // returns "18 missiles left"
IronManBlueprints.prototype.missiles = 100;
mark_V.fireMissiles() // returns "99 missiles left"
mark_VI.fireMissiles() // returns "98 missiles left"
That's why we always use this
in order to reference a specific instance property. In this case, updating the prototype property changed all the instances properties because they were referencing to the prototype object.
The [[proto]] property
There's an extra property, __proto__
, which refers to the internal [[proto]] property of instance objects. Unlike Function()
objects, every Object
has a __proto__
. It's not recommended to update the prototype
of an instance object, as prototypes are not meant to be changed on runtime (you should be able to see who's the proto of who, otherwise you need to spent extra computation in ensuring no cyclic references). Also, this is a non-standard solution and browsers are not obligated to implement it
Hopefully this helped out to clear out definitions a little bit; a prototype is just an object after all, while a constructor is the pointer to the function that created the object.
Written by Jose Jesus Perez Aguinaga
Related protips
2 Responses
Boom! Great write up! Thanks.
Thanks for IronMan.prototype.constructor = IronMan; // fixing prototype pointer.
, which helps me a lot :)