Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at cf.Objective() 2011 (Minneapolis, MN) with: Angela Buraglia

Your Javascript Constructor Logic May Break Prototypal Inheritance

By Ben Nadel on

Yesterday, I was playing around with extending the EventEmitter class in Node.js when it suddenly occurred to me that I didn't know enough about the implementation of the EventEmitter constructor. It might seem strange to worry about the constructor mechanics, as the implementation of the constructor is supposed to be encapsulated; but, my fear was due to the fact that poor constructor logic may very quickly break the underlying prototypal inheritance mechanism.

 
 
 
 
 
 
 
 
 
 

To demonstrate what I mean, let's take a look a trite example in which we have a Person() super class that is extended by a Girl() sub class:

  • <!DOCTYPE html>
  • <html>
  • <head>
  • <title>Constructor Logic Is Critical For Prototypal Inheritance</title>
  • <script type="text/javascript">
  •  
  •  
  • // I am the person constructor.
  • function Person( name ){
  •  
  • // I am the person's name.
  • this.name = name;
  •  
  • // I am the collection of traits.
  • this.traits = {};
  •  
  • }
  •  
  •  
  • // Define a class method.
  • Person.prototype.trait = function( name, value ){
  •  
  • // Check to see if we are getting or setting the
  • // given trait for this person.
  • if (arguments.length == 2){
  •  
  • // Set the trait.
  • this.traits[ name ] = value;
  •  
  • // Return this object.
  • return( this );
  •  
  • } else {
  •  
  • // Return the given triat.
  • return( this.traits[ name ] );
  •  
  • }
  •  
  • };
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // I am the girl contructor.
  • function Girl( name, age, weight ){
  •  
  • // Call the super constructor to initiate base class.
  • Person.call( this, name );
  •  
  • // Store the additional properties.
  • this.trait( "age", (age - 5) );
  • this.trait( "weight", (weight - 10) );
  •  
  • }
  •  
  • // Extend the Person class.
  • Girl.prototype = new Person();
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // Create some girl instances.
  • var sarah = new Girl( "Sarah", 32, 115 );
  • var tricia = new Girl( "Tricia", 30, 125 );
  •  
  • // Log the girls' traits.
  • console.log( "Sarah:", sarah.traits );
  • console.log( "Tricia:", tricia.traits );
  •  
  •  
  • </script>
  • </head>
  • <body>
  • <!-- Left intentionally blank. -->
  • </body>
  • </html>

As you can see, the Person() class provides a name and traits property as well as a method - trait() - for setting and getting trait values. The Girl() class extends the Person() class, inheriting the name, traits, and trait members, adding its own logic to some of the initial property storage. When we run the above code, we get the following console output:

Sarah: Object { age=27, weight=105 }
Tricia: Object { age=25, weight=115 }

As you can see, both Girl() instances are output properly.

The key to any type of inheritance, whether it be Class-based (classical) or Object-based (prototypal), is the ability to pass initialization responsibility up the chain of inheritance. Doing so allows each inherited class to fully initialize the properties that it is providing to its sub-classes. In the example above, you'll notice that the very first thing the Girl() constructor does is call its super-constructor, Person(). It is this super-class initialization call that configures the Girl()'s name and traits properties.

Now that we see how prototypal inheritance can work in Javascript, let's take a look at how it can break based on poor constructor logic. In the following demo, I'm going to keep the exact same class hierarchy; but, I'm going to change the mechanics of the super-class constructor.

  • <!DOCTYPE html>
  • <html>
  • <head>
  • <title>Constructor Logic Is Critical For Prototypal Inheritance</title>
  • <script type="text/javascript">
  •  
  •  
  • // I am the person constructor.
  • function Person( name ){
  •  
  • // Create a PRIVATE property for name.
  • var name = arguments[ 1 ];
  •  
  • // Create a PRIVATE property for traits.
  • var traits = {};
  •  
  •  
  • // Return the API for the new person instance.
  • return({
  •  
  • trait: function( name, value ){
  •  
  • // Check to see if we are getting or setting
  • // the given trait for this person.
  • if (arguments.length == 2){
  •  
  • // Set the trait.
  • traits[ name ] = value;
  •  
  • // Return this object.
  • return( this );
  •  
  • } else {
  •  
  • // Return the given triat.
  • return( traits[ name ] );
  •  
  • }
  •  
  • },
  •  
  • // For our debugging....
  • traits: traits
  •  
  • });
  • }
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // I am the girl contructor.
  • function Girl( name, age, weight ){
  •  
  • // Call the super constructor to initiate base class.
  • Person.call( this, name );
  •  
  • // Store the additional properties.
  • this.trait( "age", (age - 5) );
  • this.trait( "weight", (weight - 10) );
  •  
  • }
  •  
  • // Extend the Person class.
  • Girl.prototype = new Person();
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // Create some girl instances.
  • var sarah = new Girl( "Sarah", 32, 115 );
  • var tricia = new Girl( "Tricia", 30, 125 );
  •  
  • // Log the girls' traits.
  • console.log( "Sarah:", sarah.traits );
  • console.log( "Tricia:", tricia.traits );
  •  
  •  
  • </script>
  • </head>
  • <body>
  • <!-- Left intentionally blank. -->
  • </body>
  • </html>

As you can see, the Girl() sub-class logic is exactly the same as it was in the first demo. The only thing that we changed is the architecture of the Person() super-class and its encapsulated constructor logic. This time, however, when we run the demo, we get the following console output:

Sarah: Object { age=25, weight=115 }
Tricia: Object { age=25, weight=115 }

Notice that in this output, both Sarah and Tricia have the same age and weight. This is because both Girl() instances are actually using the same trait collection; as such, changes to one Girl() instance are reflected in the properties of the other Girl() instance. And, in fact, this replication would hold true for all future Girl() instances that we created.

This problem arises because our super constructor, Person(), fails to uphold the initialization chain required for proper inheritance. Rather than initializing its own properties - the inherited properties - the Person() constructor creates and returns an entirely new object. Because of this, the "super" aspects of the Girl() instances never get fully initialized.

A couple of times in this post, I have used the phrase, "poor constructor logic." The weight of this phrase is meant to evoke an emotional response; but the truth of the matter is, "poor" is definitely contextual. If your class is designed to be extended, then yes, poor constructor logic can very well break the underlying inheritance mechanism. If, however, your class is "final" - that is, not meant to be extended - then there really is nothing "wrong" that you can do in your own constructor (if you even have a constructor). Singletons (objects for which there will only ever be one instance) can sometimes be a good example of a "final" class.

In Node.js, the EventEmitter class is meant to be extended. As such, I think it's probably safe to assume that its constructor upholds the necessary initialization chain. If it didn't however - if the EventEmitter had poor constructor logic - I could very quickly find my sub-classing result in unexpected behavior. The key take-away here is to simply be cognizant of how you want your objects to be used; and then, to build your constructors in accordance with that goal.




Reader Comments

I can definitely see how the second example breaks the initialization process but I don't see a reason to return a different object inside a constructor. Seems like it's inefficient because by using the new keyword with your constructor JavaScript will actually create an object that is passed to the constructor but then is never consumed by your code. Instead I would just create a function that was meant to be called directly without the "new" keyword.

Hopefully that makes sense. :)

@Rob,

For object that are meant to be extended, you definitely never want to return a new object instance (other than the one implicitly created by the "new" keyword).

However, if your intent is NOT to have inheritance, there are actually some cool reasons why you might want to return an entirely new object. Specifically, it allows you to lexically bind methods to an object instance.

jQuery uses this to make their Deferred class easier to use:

http://www.bennadel.com/blog/2125-The-Power-Of-Closures-Deferred-Object-Bindings-In-jQuery-1-5.htm

By returning a new object, it allows the deferred methods to be used by reference without the calling context having to care about binding.

But, again, to be clear - that is a situation in which inheritance is not a requirement. If you are creating a super-class with the expressed intent of being sub-classed, you just have to be *very* careful about your constructor logic.

I really need to find a way to rationalize our different approaches to Proto inheritance. My idea involves minimal usage of constructors and more about replacing 'default' properties from inherited objects (I think more like proto style mixins). I would probably do something like this.

Sorry for the crazy constructor style, I wanted to preserve your initialization routine.

  • var person = {
  • type: 'Human',
  • sex: undefined
  • }
  • //This isn't a constructor
  • var girl = function( name, age ){
  • return new Girl.prototype.create( name, age );
  • }
  •  
  • girl.prototype = {
  • //This is a constructor
  • create: function( name, age ){
  • this.name = name;
  • this.age = age - 5;
  • return this;
  • },
  • prototype: person //For your demo
  • }
  • //B/c I hacked the constructor, must do this
  • girl.prototype.create.prototype = girl.prototype;
  •  
  • var Tricia = girl('Tricia', 30);
  • var Patricia = girl('Patricia', 35);

I always have concerns about how I am making use of inheritance, but I've been told OOP is too complex for mere mortals.

@Drew,

I'm having a little trouble parsing that in my head, but I think I can follow what you're doing. The girl() method is basically a factory, not a constructor. That's interesting... I have to let that sink in :)

@Ben,

I read somewhere that a factory is something that takes a string and creates an Object. This would take an array of args or an object and create a new object. So maybe this is the 'dumb' factory pattern. :)

@Drew,

Ha ha, please take my terminology with a grain of salt. I'm about as un-OOP as you can get. I can only one day hope to understand that kind of architecture.

As you can see, I easily exchange terms like "Object" and "Class" in a Javascript settings. I am sure someone will read this and be appalled at my egregious misuse of terminology -- which they are completely right about. But, like I said, I struggle just to get this far :)

@Ben,

I don't know enough either, I move from one person's opinion to another. Are there any real answers to things like what is a Factory, Observer, etc? We have the Gang of Four book as an aging reference, then nobody else challenged the ideas from that book just reiterated them...

@All,

I just wanted to write a quick follow-up to this post. In my second demo, I am using Private variables. My intent here was NOT to say that private variables break Prototypal inheritance. I only used private variables to create a context in which one *might* return an object literal from a constructor:

http://www.bennadel.com/blog/2181-Private-Variables-Do-Not-Necessarily-Break-Prototypal-Inheritance-In-Javascript.htm

As long as you object's API is this-scoped, private variables should be totally fine when used in conjunction with prototypal inheritance.

Your problem here is the way you create a prototype chain, not the logic of the constructor.

You should never create a prototype chain like this, despite what some teaching indicates.

// Extend the Person class.
Girl.prototype = new Person();

That is what causes your problem. You should instead always use the arbitrary inheritance when creating prototype chains.

Girl.prototype = Object.create(Person.prototype);

This is a very important point. See http://www.ngspinners.com/jay/

@Tim,

I would agree with you that the use of Object.create() is a "cleaner" way of defining the prototype since you avoid the risk of having to call a constructor with missing arguments. So, from that standpoint, I would definitely agree that Object.create() is a better approach.

However, from what I understand, the Object.create() method simply puts an intermediary object between your sub-class prototype and the super-class prototype without having to call the super-class constructor. I think it just does something like this:

  • function objectCreate( superClass ){
  • var f = function(){};
  • f.prototype = superClass;
  • return( new f() );
  • }

Even if you do that, the problem with the Person() constructor is that it is built on top of values that are outside the "this" scope. As such, it doesn't much matter how you construct your prototype relationships - if the constructor doesn't use the "this" scope, all approaches to the prototype chain will fail.

@Ben,

Ok, I think there are several problems that you were describing, and to be honest I didn't read thoroughly enough to look at the other problems.

What I'm pointing out is that the issue of both instances having the same value is something that can be caused because the constructor was instantiated for the prototype.

In your example the more important reason for the issue is as you point out, that the parent constructor is not putting the methods on the this object.

Also, to clarify what I said about Object.create, it isn't actually satisfactory on it's own, the Jay framework for example provides a method that does prototype chaining which uses Object.create and does one or two other things to keep the type system going, such as setting the .constructor property for the new prototype.

You make a good point that we should be very careful how we do our inheritance. All I'd like to add is that instantiating for the prototype is very bad practice as well. Being able to know how the language has been used is important, and why I think a standard library (such as Jay) is important for these concerns.

@Tim,

Good point with the ".constructor" property. I always forget about that one :) I think that messes up the typeof() operator or the instanceOf() operator (or both) I can't remember.

No worries on any of the points - I'm just happy to have good conversation about this! Plus, I really should be using Object.create() more often to help build my chains. Hopefully this will finally make it stick!

Hey Ben,

You have a great blog here and I've enjoyed numerous visits.

I wanted to comment on yours and others comments about OOP being difficult to understand and your "hope of one day understanding it". It made me laugh, because its SO much simpler than the contorted schemes employed by javascript.

There is just so much irony in you saying that, when, being an obviously sharp guy, you are applying so much effort to assure good patterns of inheritance with javascript, and at the same time under-estimating how easily you'd get OOP.

javascript is powerful and flexible, but its implementation is wacked.