Extending Classes In Object Oriented Javascript

Posted March 2, 2009 at 3:06 PM by Ben Nadel

Tags: Javascript / DHTML

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

Mar 2, 2009 at 3:39 PM // reply »
42 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.


Mar 2, 2009 at 3:47 PM // reply »
10,640 Comments

@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.


Mar 3, 2009 at 1:32 AM // reply »
42 Comments

Understood. I guess that jQuery just makes me lazy sometimes :). I like the modal window class example. That makes perfect sense. Thanks.


Mar 3, 2009 at 1:33 AM // reply »
42 Comments

I was just wondering:

What are you going to go buy in your store? You have enough points now!


Mar 3, 2009 at 8:02 AM // reply »
10,640 Comments

@Brandon,

Ha ha, yeah, I have a few points kicking around now :)


Mar 3, 2009 at 8:17 AM // reply »
26 Comments

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. :)


Mar 3, 2009 at 9:26 AM // reply »
10,640 Comments

@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.


Mar 4, 2009 at 1:14 PM // reply »
48 Comments

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.


Mar 5, 2009 at 8:50 AM // reply »
10,640 Comments

@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?


Mar 8, 2009 at 5:57 PM // reply »
1 Comments

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.


Mar 9, 2009 at 8:18 AM // reply »
10,640 Comments

@Thomas,

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


May 3, 2009 at 9:25 PM // reply »
1 Comments

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!


May 4, 2009 at 8:27 AM // reply »
10,640 Comments

@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.



Post A Comment

Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
InVision App - Prototyping Made Beautiful With Prototyping Tools Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
Feb 12, 2012 at 3:37 AM
Learning ColdFusion 8: CFImage Part III - Watermarks And Transparency
Hi Ben, Just to ask currently it is placed bottom right corner, if i need to replace the same rendered image on the bottom left side or in the bottom center, how that can be calculated. bottom ce ... read »
Feb 11, 2012 at 9:29 PM
Use jQuery's SlideDown() With Fixed-Width Elements To Prevent Jumping
I can't say how glad I am that I found your post. Thank you very much. ... read »
Feb 10, 2012 at 7:21 PM
jQuery AJAX Strips Script Tags And Inserts Them After Parent-Most Elements
Update! Instead of $(eval(options.insertAfter)).after(data['insertData']); I now use: var ajaxNode = document.createElement('span'); var parent = $(eval(options.insertAfter))[0].parentNode; ... read »
Feb 10, 2012 at 6:18 PM
jQuery AJAX Strips Script Tags And Inserts Them After Parent-Most Elements
encountered this same, what I consider, jQuery bug last week. I'm building a site in which I load some content via AJAX. This content contains Linkedin share button placeholders which Linkedin API ne ... read »
Feb 10, 2012 at 11:30 AM
Cross-Origin Resource Sharing (CORS) AJAX Requests Between jQuery And Node.js
After you understand the concepts here, this is an awesome cheatsheet for enabling CORS in just about anything http://enable-cors.org/ ... read »
JM
Feb 10, 2012 at 9:10 AM
My Safari Browser SQLite Database Hello World Example
@Amy, Here is a very good tutorial on how to use JOIN: http://www.sqltutorial.org/sqljoin-innerjoin.aspx ... read »
Feb 10, 2012 at 4:42 AM
Building A Twitter-Inspired RESTful API Architecture In ColdFusion
This is great, very useful Ben. I spotted a small typo in the api.cgm listing: <cfthrow type="Unauthroized" /> Cheers Stefan ... read »
Feb 9, 2012 at 10:35 PM
CFDirectory Filtering Uses Pipe Character For Multiple Filters (Thanks Steve Withington)
I was wondering if there would be a filter you could apply so that you got everything but what you included in the filter. As in show me all docs that are not a .pdf. ... read »