JavaScript Architecture for the 23rd Century

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…

12tb7

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.

Previous

Modern Web Best Practice: Utility Frameworks

Resources for Getting Into NodeJS

Next