Asynchronous Module Definition, or AMD, is a way of defining JavaScript classes in such a way that they can be loaded when needed, rather than loading all the classes for your system right away. One of the principle advantages of this pattern is the increased performance of the system that inherently comes from loading fewer resources. By leveraging the AMD pattern to create self-contained modules that can be loaded on demand one can build modules that have clearly defined dependencies and are highly cohesive.
Reducing coupling between modules makes it easier to manage functionality in a larger system. Modules can be removed or refactored without having to go through the trouble of identifying every single usage of that code throughout your system; the coupling points between modules are clearly defined by where the module is being required. This is doubly valuable in JavaScript where there is no clear inheritance between classes. Throughout this post we’ll explore how the AMD pattern can help JavaScript developers write more modular code that not only helps boost performance but also makes a system significantly easier to manage and expand.
For starters, let’s take a look at the way we typically write JavaScript modules. In particular, we’ll investigate at how we might write a reusable class. If you are familiar with the concept of prototypical inheritance and ‘IIFE’s then this will be a bit of a refresher for you.
var MyClass = function() {
//instantiate the class
};
MyClass.prototype = {
someFunc: function(){
//some functionality occurs
//This use of ModuleA is a dependency.
ModuleA.someFunc();
//look, another dependency!
ModuleB.someFunc();
}
};
This is a pretty standard example of prototypical inheritance. We instantiate new instances of MyClass by calling new MyClass(). This means that when invoking the methods on an instance of MyClass, the context (the ‘this’) will be that particular instance, and not every other instance that exists in memory.
But there are still some issues with this method. For one, we’re defining everything in the global namespace, which is never a good idea. Variables declared in the global namespace stay in memory and can potentially overwrite native functionality. Granted, we’re only really defining the class MyClass, but it means that every bit of functionality we define for this class must be a public method on the class itself. This could mean having a bloated class with a lot of additional methods that might not have any real use outside of being a utility method for the class itself.
We’ve also built a dependency on two other modules, ModuleA and ModuleB. We’ll discuss that issue in a bit.
So let’s try another approach to writing this class:
var MyClass = (function(){
var MyClass = function() {
//instantiate the class
};
MyClass.prototype = {
someFunc: function(){
//some functionality occurs
ModuleA.someFunc();
ModuleB.someFunc();
}
};
return MyClass;
})();
This is an example of an IIFE (immediately invoked function expression, pronounced “iffy”). This has become a fairly prevalent way of defining JavaScript classes since it has the nice benefit of keeping all of the inner workings of the class organized and private without polluting the global namespace. Actually, having your class defined in its own closure is great because anything that you define within the scope of that closure is going to be exactly what you expect it to be. Even if the same variable name is declared globally elsewhere, so long as you declare it properly within your closure (read: with a var in front of it) it’s safe.
In fact, we can leverage the fact that our class is defined in a closure to help keep track of our external dependencies as well. Since we are immediately invoking our closing function, we can also pass dependencies as arguments. Now we are clearly defining the dependencies near the top of our closure, enabling us to keep track of them within the scope of our class:
var MyClass = (function(A, B) {
var MyClass = function() {
…
}
…
return MyClass;
})(ModuleA, ModuleB);
This is a pretty popular technique to use when loading frameworks such as jQuery, Mootools or Prototype, which all use the ‘$’ as a variable name. By passing in the specific framework you want to use in your closure you can clearly define what ‘$’ actually means. This is a great way to handle migrations from one framework to the other; both frameworks can exist in the system simultaneously without fear of conflict, since the actual implementation of the framework is happening safely from within a closure.
If you’re already writing your code this way then you’ve already come a long way towards writing better JavaScript. Even so, we can take this concept farther. While we’ve scoped out our dependent modules, (and this is a good thing), our code can still fail if those modules aren’t loaded before this one. We’re bound to a strict load order. Consider this: MyClass has a dependency on ModuleA and ModuleB. ModuleA could have one or more dependencies as well, and those dependencies can have dependencies all of their own! You can see how the complexity can grow.
This is in part what happens in a tightly coupled system. We’ve created what can be described as a dependency web – so many classes and modules that are dependent on other classes and modules that if you were to draw a diagram showing the relationship between all your classes it would look something like a spider web. This is hardly a problem when you have 2 or 3 JavaScript modules that are being loaded. However, when a system starts to have more classes, this web can quickly become unmanageable. Removing or refactoring a module can become a harrowing experience that may have unexpected ramifications on the entire system.,/p>
One way of addressing this unnecessary complexity is by using the AMD pattern to define modules.
A Note on RequireJS
While there are a few AMD frameworks in existence, I’ve had the most experience with RequireJS. It’s widely used and very well documented. It can also be implemented on NodeJS projects in addition to traditional JavaScript projects. Details can be found at requirejs.org.
All the examples in this article will use RequireJS, but the concepts and patterns are consistent with other AMD frameworks.
Moving Right Along…
As I mentioned at the start of this article, the AMD pattern is a way of loading JavaScript modules when you need them. By using strings to identify dependencies we have not only a list of dependencies defined for a module right at the top, but we also have the ability to retrieve those modules if they have not already been loaded into the system. Let’s take a look at an example using MyClass:
define(‘myClass’, [‘moduleA’, ‘moduleB’], function(A, B){
var MyClass = function(){
…
};
…
return MyClass;
});
and when we want to load the module:
require([‘myClass’], function(MyClass) {
new MyClass();
});
Let’s take a moment to break down what’s going on here:
The “define” method takes three arguments; a string identifier, an array of required modules, and a function that will be invoked when the module is loaded. The function is associated with the string identifier; when that string identifier is required (as are the strings listed in the array), the function is invoked, passing in the required module as an argument. So, by listing ‘moduleA’ and ‘moduleB’ in the array of required modules, we are loading them as the arguments A and B within the scope of the function, assuming that ModuleA and ModuleB actually return something. Since we are talking about modules as classes, let’s assume they do.
The string identifier also happens to be a relative path to the module (the base path can be defined when the AMD framework is initialized; see the documentation for whichever framework you are using!). This means that the script file containing your module does not have to be loaded until that script is needed, which certainly has ramifications on performance, which we will discuss later. There are two important side effects of being able to load modules in this way: one, your code must be highly cohesive; and two, you must make sure that any dependencies are loaded prior to or as you load this module.
By highly cohesive, we mean to say that the code inside this module is specific to the functionality of the module. Any functions, anonymous or otherwise, should be necessary for the module to perform its intended task: no more, no less. It’s already a good idea to avoid including functionality that has nothing to do with the class of which it is a part. It’s similar to talking about advanced programming techniques in an American literature lecture (or vice versa); you’re probably doing something very clever, but chances are that’s not what your audience came for. When you (or another developer) are requiring your module, they are calling for a specific set of functionality. If a developer requires a module called “Carousel”, it should contain code for a carousel user interface and not a method for parsing JSON strings. If the functionality is private, then it will never be exposed outside of the scope of your module. If it’s publicly exposed, then chances are that most developers using your code may never know it’s there, and even if they do, they may question what that odd method has to do with the module you created. If you’ve included code that is outside of the define statement then you’ve defeated the purpose of using an AMD pattern, since now you are loading code that cannot be explicitly referenced inside the scope of the requiring module. Not to mention that it’s now floating in the global namespace, which, as we discussed earlier, is not a good idea. One of the goals in writing more modular code is to keep a module as specific as possible. If you find that you are writing code to perform some utilitarian function that is not inherently specific to the module containing it, consider elevating that code to its own module, or to a module containing utility methods that are reused throughout many scripts. The AMD pattern helps guide us into writing code that is designed for a singular purpose.
The AMD pattern also forces us to consider how and when our code is loaded. Consider that one of the benefits mentioned above is that the load order of files is no longer a problem; files are loaded when the module they are associated with is requested. The define method takes as an argument an array of modules that we wish to load with the module we are defining. We can load any or all of the modules on which the new module is dependent, so long as those modules have been defined as AMD modules. It behooves us to indicate which modules we wish to load with our new module; not only do we have a list of dependencies at the top of our module, we are also guaranteeing that these modules will be available as soon as we load our new module.
As a side effect, we’ve also provided a coupling point for our dependent modules. We don’t have explicit references to our dependent modules throughout our code. Note that we can define our required modules however we like within the scope of the module that requires them. This means that we can change which modules are used within a closure by simply changing what module is required. Take, for example, the following code:
require(‘myclass’, [‘moduleA’], function(A){
…
});
Consider a scenario where major refactoring has been done on moduleA to eliminate a lot of functionality that is no longer needed by newer webpages, but older pages still need the older version. The refactored version of the module is defined as ‘moduleANew’. We could search through each of the classes for references moduleA and replace each explicit reference to the module to ‘moduleANew’. We could also just change which module is required:
require(‘myclass’, [‘moduleANew’], function(A){
…
});
Now our module ‘myclass’ will use the newer, refactored code, while modules still dependent on the legacy code will not break. Furthermore, by leveraging the AMD pattern to load our modules asynchronously, there is no need to load both moduleA and moduleANew. The appropriate module will be loaded automatically.
Some Thoughts on Performance
We’ve highlighted how using the AMD pattern can improve the maintainability of your system. Now we’ll discuss how this modularity can also have an effect on the performance of your system. We’ve already discussed the potential performance gains from not loading script until it is needed. In short, loading code when it is necessary means less code allocated to memory. In the context of a website, this means that you will have a faster initial page load time. The subsequent time and memory required to load modules later on isn’t reduced, but it is distributed throughout the user’s experience and more often than not will be negligible.
Performance is not just the amount of space and time it takes to load a module; it’s also the amount of time it takes to execute the code. It is not uncommon to have a module that performs a certain action depending on a state or condition. Let’s say we’ve created a reusable module for rendering a tabbed user interface. In the interest of making it useful for multiple pages on the site, the module has code for different types of animations and button types. Each time the user interacts with the tab interface the module must determine what functionality is to be used for this implementation:
If (this.options.animated) {
If (this.options.animationMode === ‘vertical’) {
//animate vertically
} else if (this.options.animationMode === ‘horizontal’) {
//animate horizontally
} else {
//no animation mode, do the default logic
}
} else {
//default logic
}
This example highlights how potentially complex our reusable module can get. In order to make our module useful for a variety of scenarios we need to make sure it encompasses the needs of that implementation. However, every time this code is executed we must evaluate a series of conditionals to determine what the desired functionality should be. A single conditional may not seem like a huge performance hit but as our behaviors become more numerous and complex our code takes longer to execute. We could separate the different branches in our logic into multiple versions of our tab module but then we’ve defeated the purpose of having a reusable module.
Consider an alternative; if we know what functionality we need when the module is instantiated, we can use the AMD pattern to load the appropriate parts of the module when it’s needed. Imagine if, instead of having one method to change tabs encompassing all possible interactions, we required the script that extends our base class with the appropriate tab method. We’re explicitly defining what functionality our module should have without needing to re-write the entire module. Because we are leveraging an AMD pattern to load that functionality, it isn’t necessary to load all of the code that could possibly be associated with the module; we have only the code we need.
Because JavaScript doesn’t have the same notion of class inheritance that is present in other object-oriented programming languages, it falls to the developer to maintain dependencies between modules. It can become almost unrealistic in larger systems to keep track of all of the coupling points between modules. The AMD pattern goes a long way towards helping developers build systems that are loosely coupled to each other through clearly defined points of interaction. These interaction points easily identify the module’s dependencies. Moreover, it helps developers write modules that are self-contained and specific to the intended use of the module. These principles, when applied to any system, can take the insanity out of maintaining and scaling any JavaScript system and lead to code that is more reusable, more stable and performs better.