Jonathan discusses JavaScript architecture patterns of the 23rd century and what the future of JavaScript holds for the 24th century.”
JavaScript applications have grown in size and complexity for the last several years. More and more single page applications have hit the market, and demands for that type of experience have increased to the point where even Google finally decided to render JavaScript when it crawls pages.
This demand for SPA type applications has made JavaScript architecture increasingly important. JavaScript, being a dynamic language, you have to go the extra mile to ensure that the code is written in a maintainable fashion and to avoid spaghetti code.
For a long time it seemed like it was ok to just use a single file full of jQuery selectors and event handlers. This is just not a sustainable pattern. The Modern Web that we are entering here in the 23rd century demands a more thought out, and architected approach.
Architecture Patterns
An architectural pattern is not something that you sit down and write. No one sits at their desk and thinks about how to write a new shiny pattern. A pattern is something that comes about as a discovery. Some problem is solved potentially multiple times and a pattern is extracted from the solution(s).
Constructor
First of all let’s take a step back into some native JavaScript functionality that has been there for years, the constructor pattern.
function Starship() {}
Every function you declare in JavaScript can be used as a constructor to create an instance. This allows you to create reusable functions.
var enterprise = new Starship(),
IKSBuruk = new Starship();
You can modify the original function to take arguments to help describe the instance better. You can assign the arguments to properties on this
which will refer to the instance of the constructor.
function Starship(owner, operator, type) {
this.owner = owner;
this.operator = operator;
this.type = type;
}
var enterprise = new Starship('Federation', 'Star Fleet', 'Class 1 Heavy Cruiser'),
birdOfprey = new Starship('Klingon Empire', 'Klingon Imperial Fleet', 'Klingon Warship');
You can also utilize a built in feature of JavaScript constructors called the prototype
. The prototype
is simply an object. On it, you define properties and methods that will be added to every instance of a constructor.
function Starship(owner, operator, type, weapons) {
/* ... */
this.weapons = weapons;
}
Starship.prototype.fire = function(weapon) {
this.weapons[weapon].launch();
};
function PhotonTorpedoSystem() {}
PhotonTorpedoSystem.prototype.launch = function() {
console.log('launching torpedos');
};
var torpedos = new PhotonTorpedoSystem(),
var weaponSystem = {
torpedos: torpedos;
};
var enterprise = new Starship(
'Federation',
'Star Fleet',
'Class 1 Heavy Cruiser',
weaponSystem
);
enterprise.fire('torpedos') // launching torpedos;
What’s great about the prototype
system in JavaScript is it enables you to create a system of inheritance. Inheritance in JavaScript is a little bit different than in other languages, but it’s not too difficult with a bit of practice.
function ConstitutionClass(captain, firstOfficer, missionDuration) {
this.captain = captain;
this.firstOfficer = firstOfficer;
this.missionDuration = missionDuration;
Starship.apply(this, ['Federation', 'Star Fleet', 'Class 1 Heavy Cruiser', weaponSystem]);
}
ConstitutionClass.prototype = Object.create(Starship.prototype);
ConstitutionClass.prototype.constructor = ConstitutionClass;
ConstitutionClass.prototype.warp = function(speed) {
console.log('warping at: ' + speed);
};
var enterprise = new ConstitutionClass('Kirk', 'Spock', 5);
enterprise.fire('torpedos'); // Launching Torpedos
enterprise.warp(14.1); // warping at: 14.1
Here we’ve created the ConstitutionClass
constructor. This constructor takes a captain
, firstOfficer
, and missionDuration
as arguments. It then goes on to call Starship.apply
and passes in an array of arguments. This ensures that the parent constructor gets called.
Every function in JavaScript has a call
and apply
method that allows you to invoke a function by passing in a context
and, in the case of apply
, an array of arguments to use when invoking the function.
Next, to properly tell the ConstitutionClass
to inherit from Starship
we simply use Object.create
to assign the prototype of ConstitutionClass
.
What this does is tell ConstitutionClass
that it’s a type of Starship
by adding all of the Starship
prototype properties and methods to the ConstitutionClass
prototype.
Check out how this looks when you run it and open up the console…
You can easily see how the enterprise
instance of ConstitutionClass
has correctly been assigned its properties, as well as the properties of a Starship
.
Notice also how the ConstitutionClass
has a warp method as well as a fire
method that it inherits from the Starship
.
That’s prototypical inheritance.
IIFEs to prevent global leaks
An important thing to note about the code above is there are several global leaks occurring. When you simply declare a function or variable in a JavaScript file, it will automatically be added to the global namespace (the window
in the case of a browser);
This code here will create several globals…
function PhotonTorpedoSystem() {}
PhotonTorpedoSystem.prototype.launch = function() {
console.log('launching torpedos');
};
var torpedos = new PhotonTorpedoSystem(),
var weaponSystem = {
torpedos: torpedos;
};
After running this code you will have created window.PhotonTorpedoSystem
, window.torpedos
, and window.weaponSystem
. This is not an ideal scenario.
One great pattern for avoiding this issue is to utilize an Immediately Invoked Function Expression…
(function(global) {
function PhotonTorpedoSystem() {}
PhotonTorpedoSystem.prototype.launch = function() {
console.log('launching torpedos');
};
var torpedos = new PhotonTorpedoSystem(),
var weaponSystem = {
torpedos: torpedos;
};
/* Create instance of enterprise etc */
global.PhotonTorpedoSystem = PhotonTorpedoSystem; // Export just this to the window
}(window));
Here you can see the (function(global) {
at the beginning and the }(window));
at the end. This causes the anonymous function function(global)
to fire right away, and sets global
to the window
. This all works because the IIFE creates a closure. Everything defined inside the IIFE is contained in the memory for that function alone unless it gets exported on the global
we defined.
Now you can explicitly set things on the window
for use in other JavaScript files.
This is a step in the right direction, but another even better pattern to utilize here would be the Namespace Pattern.
// weaponSystems.js
(function(global, NS) {
NS.Weapons = NS.Weapons || {};
function PhotonTorpedoSystem() {}
PhotonTorpedoSystem.prototype.launch = function() {
console.log('launching torpedos');
};
/* ... */
NS.Weapons.PhotonTorpedoSystem = PhotonTorpedoSystem; // Export just this to the NS.Weapons namespace
}(window, window.NS = NS || {});
Notice how we’ve changed the bottom of the IIFE to }(window, window.NS = NS || {});
. This will pass the window
, as well as an NS
object. This slightly odd notation here says, “pass NS if it exists, or create a new object for it”.
You can also see this syntax used again here NS.Weapons = NS.Weapons || {};
.
It’s effectively the same thing as using an if statement, but is more terse.
if (!NS.Weapons) {
NS.Weapons = {};
}
You can add whatever objects and functions you want to on to the namespace. This will allow you to avoid adding too many globals to the window.
(function(global, NS) {
NS.Ships = NS.Ships || {};
function Starship(owner, operator, type, weapons) { /* ... */ }
function ConstitutionClass(captain, firstOfficer, missionDuration) { /* ... */ }
/* ... */
NS.Ships.Starship = Starship;
NS.Ships.ConstitutionClass = ConstitutionClass;
}(window, window.NS = NS || {});
IIFE for the Revealing Module Pattern
Another pattern for creating functions is the “revealing module pattern”.
NS.Owners.starFleet = (function() {
var ships = [];
var addShip = function(ship) {
ships.push(ship);
};
var removeShip = function(ship) {
var index = ships.indexOf(ship);
if (index > -1) {
ships.splice(index, 1);
}
};
var getTotal = function() {
return ships.length;
};
return {
addShip: addShip,
removeShip: removeShip,
totalShipsInFleet: getTotal
};
}());
This pattern allows you to define the public API for an object as well as have a few private members because of the IIFE. In this case, ships
stays private because it’s defined within the IIFE, whereas addShip
and removeShip
are exported via the object in the return
statement.
(function() {
var enterprise = new NS.Ships.ConstitutionClass('Kirk', 'Spock', 5);
NS.Owners.starFleet.addShip(enterprise);
NS.Owners.starFleet.getTotal(); // 1
}());
Now you can use NS.Owers.starFleet
to add ships to Star Fleet.
Organizing Files
Architectural patterns are very helpful for creating maintainable web applications. One further problem exists though in large scale applications, and that’s file organization.
In the early days, having all of your code in a single Javascript file was still not a great idea, but it could work because there wasn’t that much code. Today in the 23rd century, that’s just not the case. There are hundreds, if not thousands, of lines of code necessary for a web application. Having a single JavaScript file is just not feasible.
When you start adopting architectural patterns like constructor or module patterns, your code will have been broken down into smaller units. This is great for multiple reasons. For one, you’ll now be able to move these small units in to their own files. And it makes unit testing much easier.
As a goal, try to keep each JavaScript file containing one logical “class” or module.
You may have something like this.
/js/
/js/app.js
/js/starship.js
/js/constitutionClass.js
/js/starFleet.js
This is much nicer. You’re able to logically divide up each piece of functionality. After a while though, you may have too many JS files in the /js
folder and may want to consider some subfolders.
There are only two hard things in Computer Science: cache invalidation and naming things.
— Phil Karlton
Naming things is hard, so when getting ready to divide your application up into subfolder, just pick some standards for you and your team and follow them. Consistency always wins!
/js/
/js/app.js
/js/classes/starship.js
/js/classes/constitutionClass.js
/js/core/starFleet.js
/js/infrastructure/..js
/js/templates/..js
Another common organizational strategy is to break up large pieces of functionality into their own folders.
/js/
/js/app.js
/js/ships/starship.js
/js/ships/constitutionClass.js
/js/core/starFleet.js
/js/captainsLog/..js
/js/weaponSystems/..js
No matter what you choose, the idea is to maintain consistency within your structure for your team. There’s no wrong answer here, just pick something and go with it.
One hiccup you’ll find breaking up your logic into many files though is dealing with loading them into your HTML pages.
With a structure like this you’ll end up having to have many script
includes that have to be in a very specific order to avoid errors.
<script src="/js/app.js"></script>
<script src="/js/core/starFleet.js"></script>
<script src="/js/weaponSystems/photonTorpedos.js"></script>
<script src="/js/starship.js"></script>
<script src="/js/constitutionClass.js"></script>
If any one of those files gets included in the wrong order, you’ll run into problems. You also will have many synchronous script includes that slow your page render.
Enter AMD.
AMD
Asynchronous Module Definition is a specification created by the CommonJS group to handle these types of issues. It defines a specification for creating and requiring modules into an application.
One of the best implementations of AMD is require.js.
With require.js, you create many modules just like we have been working with so far, but there’s a wrapper for each module. This wrapper not only hides globals from the window, but also helps define a dependency chain which allows you to have a single script
tag on your page. There is also a great build tool called r.js
that can compile all your modules to a single minified file.
To start, download require.js and include it in your page.
<script src="/vendor/require.js" data-main="/js/main"></script>
The data-main
here points to a main.js
file in our /js
directory. This main
file is responsible for some initial setup that tells require
a bit about our application and kicks off the process of loading in files.
require.config({
paths: {
'foo': '/vendor/foo'
}
});
require(['app'], function(app) {
app.init();
});
Here is a very simple main
file. The require.config
can set up some aliases if you need them. Then you use the require
method by passing in an array of module names. The module names correspond directly to their file names minus the .js
extension. Here we kick off the application by loading /js/app.js
.
app.js
would look something like…
define(function(require) {
var ConstitutionClass = require('ships/constitutionClass');
starFleet = require('core/starFleet'),
captainsLog = require('captainsLog/log');
return {
init: function() {
var enterprise = new ConstitutionClass('Kirk', 'Spock', 5);
starFleet.addShip(enterprise);
captainsLog.record('Stardate 43125.8. Commence 5 year mission into deep space');
}
};
});
This module is defined with the commonjs syntax. There are 2 ways you can export an API with requre.js’s commonjs syntax as well. Either via the return statement as above, or via the actual commonjs
way of using module.exports
.
module.exports = {
init: function() {
var enterprise = new ConstitutionClass('Kirk', 'Spock', 5);
starFleet.addShip(enterprise);
captainsLog.record('Stardate 43125.8. Commence 5 year mission into deep space');
}
};
Another quick note about require.js. All the examples below are going to use the commonjs syntax for creating modules, however you can either use the commonjs syntax or use a syntax like this…
define([
'ships/constitutionClass',
'core/starFleet',
'captainsLog/log'
], function(ConstitutionClass, starFleet, captainsLog) {
return {
init: function() {
var enterprise = new ConstitutionClass('Kirk', 'Spock', 5);
starFleet.addShip(enterprise);
captainsLog.record('Stardate 43125.8. Commence 5 year mission into deep space');
}
}
});
Rather than a function that asks for the require function function(require)
, you can pass in an array of dependencies. The modules are loaded in and are passed in as the arguments to the function. function(ConstitutionClass, starFleet, captainsLog) {
. This is important to know because what require.js actually does is convert the commonjs syntax into this syntax for you.
Either syntax is perfectly valid, so choose which you prefer. The commonjs syntax is nice though because in theory you could share your modules with node, and/or browserify which we’ll talk about later.
Then the ships/constitutionClass.js
would look like…
define(function(require) {
var Starship = require('ships/starship');
function ConstitutionClass() { /* ... */}
return ConstitutionClass;
});
This module returns a constructor function just like we created above, except now there’s no need for any namespacing because you’re working with a module that keeps everything private except what you return from the module.
You can also see how the dependency chain is beginning to form. app.js
requires ships/constitutionClass.js
which then requires ships/starship.js
. This will continue on and on down the chain of dependencies.
A module like core/starFleet.js
might look like this…
define(function() {
var ships = [];
var addShip = function(ship) {
ships.push(ship);
};
var removeShip = function(ship) {
var index = ships.indexOf(ship);
if (index > -1) {
ships.splice(index, 1);
}
};
var getTotal = function() {
return ships.length;
};
return {
addShip: addShip,
removeShip: removeShip,
totalShipsInFleet: getTotal
};
});
Effectively the same thing as the revealing module pattern gave us. Simply return an object literal with a public API.
Browserify
Browserify is another method of creating JavaScript modules. With browserify, you basically write commonjs modules and run browserify
with the global node.js module and it will bundle up your code for use in the browser.
The syntax of creating modules with require and browserify is very close if you choose to use the commonjs syntax and the module.exports
when working with require.
The app.js
above written as an actual commonjs module looks like…
var ConstitutionClass = require('ships/constitutionClass');
starFleet = require('core/starFleet'),
captainsLog = require('captainsLog/log');
module.exports = {
init: function() {
var enterprise = new ConstitutionClass('Kirk', 'Spock', 5);
starFleet.addShip(enterprise);
captainsLog.record('Stardate 43125.8. Commence 5 year mission into deep space');
}
};
The only difference is that you use module.exports
to export your public API rather than a return statement.
To use browserify, install it with npm…
npm install -g browserifylanguage-shell
Then you run it…
browserify main.js > bundle.js
This will output a single bundle.js
file for use in the browser. There are a lot of advantages to both browserify and require.js. Module loading helps break up your application and no matter which you choose, it will help you create a more maintainable application.
ES6
One of the many awesome features coming in ES6 or ES.next is built-in classes with native JavaScript. ECMAScript is currently working on the spec for classes. You can view the unofficial draft of the spec.
The new spec for JavaScript defines a new way of creating classes in JavaScript. If we take our previous examples and write them using ES6, the Starship
class would look like…
class Starship {
constructor(owner, operator, type, weapons) {
this.owner = owner;
this.operator = operator;
this.type = type;
this.weapons = weapons;
}
fire(weapon) {
this.weapons[weapon].launch();
}
}
You can see immediate differences here. First is the class
identifier. Then you can see how you define a constructor with constructor
and methods by simply a methodname(a, b, c)
signature. No need to use function
anywhere here.
Inheritance is also much easier. The ConstitutionClass
can easily inherit from the Starship
using extends
…
class ConstitutionClass extends Starship {
constructor(captain, firstOfficer, missionDuration) {
this.captain = captain;
this.firstOfficer = firstOfficer;
this.missionDuration = missionDuration;
super('Federation', 'Star Fleet', 'Class 1 Heavy Cruiser', {
torpedos: new PhotonTorpedoSystem()
});
}
warp(speed) {
console.log('warping at: ' + speed);
}
}
You’ll also notice super
here which is how you call the parent method and takes the place of Starship.apply(this, [/* ... */])
.
This new class
syntax is incredibly exciting for the future of JavaScript. Having built in classes is something JavaScript has needed for a while, and this new syntax is looking great.
Traceur
There is no telling when this class
syntax will make it into browsers, but fortunately there’s a tool called Traceur that allows us to work with new features of JavaScript before they release.
You can install traceur with npm…
npm install -g traceur
And you can compile ES6 file down to ES5 for use today.
traceur --out build.js --script starship.js
Just note that if you plan on using it in the browser you must also include the runtime file above your includes…
<script src="bin/traceur-runtime.js"></script>
Hopefully ES6 classes is the future of how we’ll be writing maintainable JavaScript in the future.
Conclusion
JavaScript is an ever-changing language. As it grows there will be more and more ways to use it, and architect it. The architectural patterns above are a great way you can create JavaScript applications today, while the class
syntax will be the way of things in the 24th century. Keep JavaScripting, and live long and prosper.