Skip to main content
Ben Nadel at InVision In Real Life (IRL) 2018 (Hollywood, CA) with: Cristoffer Gallardo
Ben Nadel at InVision In Real Life (IRL) 2018 (Hollywood, CA) with: Cristoffer Gallardo

Extending Classes In A Modular JavaScript Application Architecture Using RequireJS

By
Published in Comments (7)

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.

Want to use code from this post? Check out the license.

Reader Comments

1 Comments

Very good post. I am just beginning to look at JavaScript as a real option for implementing the UI of a complex application. Coming from a Java background, the type of info found in this article is needed to have the smoothest transition possible; JavaScript is a very, very different beast from Java altogether.

15,841 Comments

@Fab,

I don't do too much Java programming; but, coming from the ColdFusion world, I enjoy dipping into the Java layer (ColdFusion is built on top of Java) in order to use things like the java.util.regex classes or jSoup or whatever. When I look at Java code, one of the things that always strikes me is the "import" statements - the ability to "load" other class definitions for use within in a class.

I really like the RequireJS allows for this kind of class-dependency loading. Glad you found this post helpful!

1 Comments

Wondered why do you return (this) from the constructor? could you explain?

many thanks for this post and for the series! fascinating.

3 Comments

@Lior,

The default return value from a constructor function is the new object. So, he didn't really need to return "this" but it doesn't hurt either.

1 Comments

Thanks for this writeup with examples. I just started using require and it hadnt even occurred to me that you could return constructors from a module. I will begin using this pattern right away! thanks!

1 Comments

Thank you for the article! Allowed me to get my models in-line using require and sparked an idea in the process. I found a much easier way to do this in coffeescript for anyone interested. A short example just to give an idea:

Base:

define (require) ->
	class Model
		constructor: (data) ->
			# do stuff
			data.map (e) ->
				e
 
		@::parentFn ->
			# do stuff
 
	Model

Extending:

define (require) ->
 
	Model = require('./model')
 
	class User extends Model
 
		constructor: (data) ->
			super(data)
			# do stuff
 
		@::childFn = ->
			@parentFn()
			# do stuff
1 Comments

In our company we came to the same conclusion and since then we have used this way of organizing our code with RequireJS. It's nice to see others get to the same conclusion :)

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel