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 TechCrunch Disrupt (New York, NY) with: Aaron Foss

Extending Classes In Object Oriented Javascript

By Ben Nadel on

After spending so much time down in Florida learning about Object Oriented Programming (OOP) with Hal Helms, I wanted to try and transfer some of that object oriented knowledge over to Javascript. In the past, I have definitely created Classes in Javascript by using functions as first class objects; but, I have not done anything in the ways of creating base classes and then extending and overriding those base classes.

As it turns out, extending base classes in Javascript is actually quite straightforward. The main difference is that rather than using a Class-based extension system, it uses a Prototype-based extension system. I am not going to go into what the Prototype chain is at this moment as there are parts of it that I don't fully understand (and I don't want to misinform anyone). But, from what I can gather, to take advantage of class extension, you merely set an instance of your base class as the prototype to your extending class. This way, when you need to access something in the base class, the extending class will know where to look.

For this example, I am going to create a Person class and a DatingStrategy class. The DatingStrategy class is meant to be an abstract base class that has two sub-classes: StraightDatingStrategy and GayDatingStrategy. I know this example is kind of contrived, but I figured it would be easy to visualize. The different dating strategies take the various genders involved to make decisions about dating.

  • <script type="text/javascript">
  •  
  • // Define the base dating strategy.
  • function DatingStrategy(){
  • // Nothing to do here.
  • }
  •  
  • // Method determines if the given person can date the
  • // passed-in person.
  • DatingStrategy.prototype.WillDate = function(
  • objPersonA,
  • objPersonB
  • ){
  • return( false );
  • }
  •  
  •  
  • // ------------------------------------------------ //
  • // ------------------------------------------------ //
  •  
  •  
  • // Define a straight dating strategy.
  •  
  • // Extend the dating strategy class.
  • StraightDatingStrategy.prototype = new DatingStrategy();
  •  
  • // Define the straight dating strategy class.
  • function StraightDatingStrategy(){
  • // Call super constructor.
  • DatingStrategy.apply( this, arguments );
  • }
  •  
  • // Override the will-date logic.
  • StraightDatingStrategy.prototype.WillDate = function(
  • objPersonA,
  • objPersonB
  • ){
  • return( objPersonA.Gender != objPersonB.Gender );
  • }
  •  
  •  
  • // ------------------------------------------------ //
  • // ------------------------------------------------ //
  •  
  •  
  • // Define a gay dating strategy.
  •  
  • // Extend the dating strategy class.
  • GayDatingStrategy.prototype = new DatingStrategy();
  •  
  • // Define the gay dating strategy class.
  • function GayDatingStrategy(){
  • // Call super constructor.
  • DatingStrategy.apply( this, arguments );
  • }
  •  
  • // Override the will-date logic.
  • GayDatingStrategy.prototype.WillDate = function(
  • objPersonA,
  • objPersonB
  • ){
  • return( objPersonA.Gender == objPersonB.Gender );
  • }
  •  
  •  
  • // ------------------------------------------------ //
  • // ------------------------------------------------ //
  •  
  •  
  • // Define the base Person class constructor.
  • function Person( strName, strGender, objDatingStrategy ){
  • this.Name = strName;
  • this.Gender = strGender;
  • this.DatingStrategy = objDatingStrategy;
  • }
  •  
  • // Method determines weather this person will date the
  • // passed-in person. Use a dating strategy if available
  • // or return false otherwise.
  • Person.prototype.WillDate = function( objPerson ){
  • if (this.DatingStrategy){
  •  
  • // Using dating strategy to determine whether
  • // or not to date date given person.
  • return(
  • this.DatingStrategy.WillDate( this, objPerson )
  • );
  •  
  • } else {
  •  
  • // No strategy provided.
  • return( false );
  •  
  • }
  • }
  •  
  • </script>

As you can see from the above code, we are extending the base strategy class by setting the prototype of the given sub-class:

  • // Extend the dating strategy class.
  • StraightDatingStrategy.prototype = new DatingStrategy();

Think of the Prototype chain as the extension chain; any time you don't have a given function or property, the Javascript engine checks your Prototype (base class) for the given function or property.

The one caveat that I seem to be finding (and maybe I just have not found the correct methodology) is that you have to override the constructor of your base class (your Prototype). Even if your base constructor is not meant to be overwritten, you still need to define a sub-class constructor. The good news is that calling your super constructor is really easy! All you have to do is use the apply() method:

  • function StraightDatingStrategy(){
  • // Call super constructor.
  • DatingStrategy.apply( this, arguments );
  • }

The Javascript apply() method allows us to invoke a given function (our base constructor in this case) and pass in the context in which it will execute. So, in the code above, what we are asking the Javascript engine to do is to execute the DatingStrategy() method (base constructor) using the StraightDatingStrategy instance as the THIS context. We are then forwarding the arguments collection to the super constructor for use (even though my example doesn't have any arguments).

In ColdFusion, this would be the same as calling:

  • <cfset SUPER.Init( ARGUMENTS ) />

As you can see, there's not a whole lot to extending classes in Javascript.

Now that we have our classes defined, let's create several People instances, each with different genders and dating strategies and then check to see how they will interact:

  • <script type="text/javascript">
  •  
  • // Now that we have our various classes, let's try
  • // testing the different strategies.
  • function TestDating( objPersonA, objPersonB ){
  • document.write( objPersonA.Name + " will " );
  •  
  • // Check dating.
  • if (!objPersonA.WillDate( objPersonB )){
  • document.write( "not " );
  • }
  •  
  • document.write( "date " + objPersonB.Name );
  • document.write( "<br />" );
  • }
  •  
  •  
  • // Create an instance of the straight dating strategy.
  • var objStraightDatingStrategy = new StraightDatingStrategy();
  • var objGayDatingStrategy = new GayDatingStrategy();
  •  
  •  
  • // Create some people and given them various dating
  • // strategies that we can test against.
  • var objBoyA = new Person(
  • "Straight Guy",
  • "M",
  • objStraightDatingStrategy
  • );
  •  
  • var objBoyB = new Person(
  • "Gay Guy",
  • "M",
  • objGayDatingStrategy
  • );
  •  
  • var objGirlA = new Person(
  • "Straight Gal",
  • "F",
  • objStraightDatingStrategy
  • );
  •  
  • var objGirlB = new Person(
  • "Gay Gal",
  • "F",
  • objGayDatingStrategy
  • );
  •  
  •  
  • // Test various cross-products.
  • TestDating( objBoyA, objBoyB );
  • TestDating( objBoyA, objGirlA );
  • TestDating( objBoyA, objGirlB );
  •  
  • TestDating( objBoyB, objBoyA );
  • TestDating( objBoyB, objGirlA );
  • TestDating( objBoyB, objGirlB );
  •  
  • TestDating( objGirlA, objBoyA );
  • TestDating( objGirlA, objBoyB );
  • TestDating( objGirlA, objGirlB );
  •  
  • TestDating( objGirlB, objBoyA );
  • TestDating( objGirlB, objBoyB );
  • TestDating( objGirlB, objGirlA );
  •  
  • </script>

When we run the above code, we get the following output:

Straight Guy will not date Gay Guy
Straight Guy will date Straight Gal
Straight Guy will date Gay Gal
Gay Guy will date Straight Guy
Gay Guy will not date Straight Gal
Gay Guy will not date Gay Gal
Straight Gal will date Straight Guy
Straight Gal will date Gay Guy
Straight Gal will not date Gay Gal
Gay Gal will not date Straight Guy
Gay Gal will not date Gay Guy
Gay Gal will date Straight Gal

As you can see, the sub-classed dating strategies are used easily. Now, seeing as our base class, DatingStrategy, is completely overridden by the sub-classes, it's not the best example; but, I will hopefully have more on Object Oriented Javascript in the future that will have some more meaningful examples.




Reader Comments

Although I would have to say that your example weirdest example that I have ever seen, I think that it works well in proving your point. I don't think that enough of us have really looked into writing and extending classes in javascript.

One of my questions here, however, is why wouldn't you just use ColdFusion to power the logic, and use jQuery's .load (or .post or whatever) to power the ajax. It seems to me a lot easier to have the server-side do all of the dirty work, and use javascript as the go-between.

@Brandon,

Ha ha, I couldn't think of any other examples for some reason that were small enough. I was going to go with BlackJack players and "hitting strategies" but that would have gotten too complex.

While I think off-loading some logic to ColdFusion would be good sometimes, I think there are going to be a lot of times when you want to have Javascript classes that control HTML elements. At least, that is where I am going in my head.

For example, I might have a generic ModalWindow class or a DataGrid class that handles some core functionality for those types of elements. But then, I might want to sub-class an only override certain methods.

For example, I might have a ModalWindow know how to show and hide itself; but, I might have an AddContactModalWindow that extends the ModalWindow base class and handles it's own specialized form display / submission.

Just off the top of my head. But, I just felt that I needed to get the Javascript OOP knowledge in there before I could fully contemplate how to leverage it.

If you're interested, Prototype does a pretty good job of emulating class-based inheritance.

The example given at http://www.prototypejs.org/learn/class-inheritance
is:

// properties are directly passed to `create` method
var Person = Class.create({
initialize: function(name) {
this.name = name;
},
say: function(message) {
return this.name + ': ' + message;
}
});

// when subclassing, specify the class you want to inherit from
var Pirate = Class.create(Person, {
// redefine the speak method
say: function($super, message) {
return $super(message) + ', yarr!';
}
});

var john = new Pirate('Long John');
john.say('ahoy matey');
// -> "Long John: ahoy matey, yarr!"

Class.create() is a Prototype Object method call that takes an optional superclass and a struct of methods. super.init() is accessed through the use of $super().

Not gonna get into the whole better/worse debate, but I do find this a lot easier to use. And kind of interesting that I was reading through Prototype's classes and inheritance docs on the same day you posted this article. :)

@Matt,

That method seems interesting. I guess it is really treating the class an object and then overwriting the property values, much like the $.extend() method in jQuery.

To me, this way seems to be much more wordy. But, to each his own - I don't know enough about the Prototype library of the Javascript prototype chain to say better or worse.

There is a difference between prototype extension and class based objects. We are looking to find the best scenario of class based objects at my company. We are not 'against' prototype extensions per se. We are just much happier using class based objects.

@John,

That sounds interesting. I am new to this concept in Javascript. Can you maybe give a few words as to why you are going one way and not the other?

Thanks Ben !

I have a small javascript class hierarchy for our aspdotnet input-controls, built using the ProtoType framework(like Matt mentioned), but needed to convert to jQuery(for various reasons), and this seems like the way to go for keeping my js OO.

@Thomas,

I am liking it a lot so far. Thinking in terms of classes has helped me to factor out repeated code nicely.

I got an "undefined" error when extending a function's protoype before actually declaring the function, as in your example:

StraightDatingStrategy.prototype = new DatingStrategy();

function StraightDatingStrategy(){
DatingStrategy.apply( this, arguments );
}

That was easily resolved though. Nice work!

@Ginx,

To be honest, I was surprised that that worked when I saw it in examples; I liked it because it sort of showed the extensions before the class definitions. But, if it doesn't work, then moving it after is all good. What kind of browser do you use, just out of curiosity.

Hi.

I might be a little new to Javascript, but how can you add a prototype to a subclass before declaring it?

StraightDatingStrategy.prototype = new DatingStrategy();

When I try this in firefox, there is an error...
Is there some declarations you were missing from another file to be loaded first?