Extending Classes In A Modular JavaScript Application Architecture Using RequireJS

Posted January 26, 2012 at 10:25 AM by Ben Nadel

Tags: Javascript / DHTML

Yesterday, I tried to apply some deep thinking to how dependencies should be managed in a modular JavaScript application architecture that is using RequireJS. The conclusion that I came to was that RequireJS should manage and load "definitions" while your application should manage and load "instances." This makes sense since instantiation is the domain of your business logic, not your organizational framework. When it comes to extending classes in a RequireJS context, the same principle holds true, but things seem a little bit more interesting. Base classes, which may have static functionality, are still just "definitions" that can be managed by the RequireJS framework.

NOTE: In JavaScript, most values are technically "instances" at the language level. When I refer to "instances" in this post, I am referring to objects that need to be instantiated using the "new" keyword.

To look at inheritance in a RequireJS context, I'm going to create a simple class, Friend. Friend has a single method, getName(). The Friend class inherits from the core Model class which provides the core method, getInstanceID().

Friend()

  • getName()

Model()

  • getInstanceID()

Before we look at how the Friend or the base Model class are coded, let's look at the demo that makes use of the two classes:

  • // Load the application.
  • require(
  • [
  • "./model/friend"
  • ],
  • function( Friend ){
  •  
  •  
  • // Create a few instances of Friend so that we can see
  • // how inherited functionality has been propogated.
  • var friends = [
  • new Friend( "Sarah" ),
  • new Friend( "Tricia" ),
  • new Friend( "Joanna" )
  • ];
  •  
  • // Now, iterate over each friend to output the name (which
  • // is part of the FRIEND class) and the ID (which is part of
  • // the inhereted MODEL class).
  • for (var i = 0 ; i < friends.length ; i++){
  •  
  • console.log(
  • friends[ i ].getName(),
  • ":",
  • friends[ i ].getInstanceID()
  • );
  •  
  • }
  •  
  •  
  • }
  • );

The main code demo relies only on the Friend module directly. The core Model module is a dependency of the Friend module and will be loaded automatically by the RequireJS framework. As you can see from the code, we are creating a few instances of Friend and then invoking both the sub-class method, getName(), and the core method, getInstanceID(). And, when we run the above code, we get the following console output:

Sarah : 1
Tricia : 2
Joanna : 3

So far, so simple - I think you get the idea.

Now, let's look at the core Model module. This class is intended to be sub-classed by all other model classes within the application:

model.js - Our Core Model Class

  • // Define the base / core Model class.
  • define(
  • function(){
  •  
  •  
  • // I am the internal, static counter for the number of models
  • // that have been created in the system. This is used to
  • // power the unique identifier of each instance.
  • var instanceCount = 0;
  •  
  •  
  • // I get the next instance ID.
  • var getNewInstanceID = function(){
  •  
  • // Precrement the instance count in order to generate the
  • // next value instance ID.
  • return( ++instanceCount );
  •  
  • };
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // I return an initialized object.
  • function Model(){
  •  
  • // Store the private instance id.
  • this._instanceID = getNewInstanceID();
  •  
  • // Return this object reference.
  • return( this );
  •  
  • }
  •  
  •  
  • // I return the current instance count. I am a static method
  • // on the Model class.
  • Model.getInstanceCount = function(){
  •  
  • return( instanceCount );
  •  
  • };
  •  
  •  
  • // Define the class methods.
  • Model.prototype = {
  •  
  • // I return the instance ID for this instance.
  • getInstanceID: function(){
  •  
  • return( this._instanceID );
  •  
  • }
  •  
  • };
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // Return the base Model constructor.
  • return( Model );
  •  
  •  
  • }
  • );

To me, this module is really interesting! Not only are we defining a constructor and then returning it (as the module definition), we are also providing public and private static functionality. The private, static functionality manages the internal counter for instance creation; the public, static functionality - Model.getInstanceCount() - provides a window into the private static data.

This kind of module can start to blur the line between "definition" and "instance" (in the context of this conversation). On the one hand, it defines the core Model class; however, on the other hand, it clearly provides data and behavior - the characteristics of an object that you would typically consider an "instance."

Using our previous conclusions about management responsibilities, we'll have to consider this module a "definition." Since nothing is being instantiated using the "new" keyword, we'll defer to RequireJS to load this class as a dependency is subsequent modules.

Now, let's take a look at the Friend class. Since Friend extends Model, Model is clearly a dependency for the full definition of Friend. And, since Model is, itself, a "definition" not an instance, we'll use RequireJS to manage and load the Model module for the Friend module.

friend.js - Our Friend Class

  • // Define the Friend model class. This extends the core Model.
  • define(
  • [
  • "./model"
  • ],
  • function( Model ){
  •  
  •  
  • // I return an initialized object.
  • function Friend( name ){
  •  
  • // Call the super constructor.
  • Model.call( this );
  •  
  • // Store the name.
  • this._name = name;
  •  
  • // Return this object reference.
  • return( this );
  •  
  • }
  •  
  •  
  • // The Friend class extends the base Model class.
  • Friend.prototype = Object.create( Model.prototype );
  •  
  •  
  • // Define the class methods.
  • Friend.prototype.getName = function(){
  •  
  • return( this._name );
  •  
  • };
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // Return the base Friend constructor.
  • return( Friend );
  •  
  •  
  • }
  • );

As you can see, we are using RequireJS to load the Model class for use within the Friend module definition.

With the various types of dependencies found within an application, it can sometimes get confusing as to who is responsible for loading which dependency. Mix in the object-based nature of JavaScript and the matter can become even more confusing. Modules that appear to provide both definition and functionality challenge our understanding of what we consider an "instance." In my dialogue, I refer to an instance as that which is explicitly instantiated by the application. As such, base classes - which provide static, instance-like functionally - are still "definitions" that should be managed and loaded by the RequireJS framework.




Reader Comments

There are no comments posted for this web log entry.

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 22, 2012 at 7:59 PM
Ask Ben: Simple Data Caching In ColdFusion
Hi Ben any suggestions on how to improve the following: <cffunction name="CacheUsers" access="public" output="no" returntype="void"> <cfquery name=& ... read »
Feb 22, 2012 at 6:14 PM
Using CAPTCHA In ColdFusion 8
@devlosh, Unfortunately I do not have a solution for this yet, sorry! ... read »
Feb 22, 2012 at 6:11 PM
Using CAPTCHA In ColdFusion 8
@devlosh, ... read »
Feb 22, 2012 at 2:36 PM
ColdFusion 10 Beta - Closures And Components And The THIS Scope
@Tyler, What we really need is our friendly call() and apply() methods from JavaScript ! That would be sweet. I haven't played with the invoke() method, but I assume it's just the CFScript-based eq ... read »
Feb 22, 2012 at 2:14 PM
ColdFusion 10 Beta - Closures And Components And The THIS Scope
Hey Ben, it is funny you post this as I just ran into an issue here. http://tylerclendenin.com/2012/02/coldfusion-zeus-10-beta-function-expressions-and-closures-not-quite-there/ I posted two bug ... read »
Feb 22, 2012 at 2:09 PM
Back To The Fusion - Part X
Ben, didn't I see you tweet something about a beta? I can't believe it's been a year since you posted this! ... read »
Feb 22, 2012 at 1:38 PM
Experimenting With jQuery's Queue() And Dequeue() Methods
Excellent explanation, thanks for the post. I have only one question, how do i get to the last effect in queue without execute others effect. ... read »
Feb 22, 2012 at 9:39 AM
Ask Ben: Reading In A File Using CFFile And CFInclude
@GrumpyCFer, that may surprise you (what you "can't imagine"), but it really would not surprise me at all. The things people think and assume about CF (wrongly, to their peril), stuns me. ... read »