The Power of the Module Pattern in JavaScript

Christopher T.
November 1st, 2019

In JavaScript, a widely used and powerful pattern is the Module Pattern. It can be incredibly simple to implement but the fact that it enables developers to encapsulate their code makes it one of the most versatile patterns to build robust code. When you look inside the source code of JavaScript libraries, you're most likely looking at an implementation of this pattern--and they're most likely a singleton object meaning that only one instance exists throughout the lifetime of an app.

It may be difficult for newcomers in JavaScript to understand the module pattern as there are several variations that exist. However, it's worth all the time and trouble because you'll be using the module pattern very often to make your app more robust.

Modules

As you may have guessed, the module pattern lets you create modules. In the end, modules are basically just objects. But there are a couple of ways to create modules.

The most basic way to create a module is to assign an object to a variable like so:

const myModule = {
  drive() {
    console.log('*drives*')
  },
}

A simple image representation:

plain module

Things start to become more interesting when we utilize some of JavaScript's unique features to create a module, which we will cover next.

Immediately Invoked Function Expression

Arguably the most popular variation of the module pattern is the IIFE (Immediately Invoked Function Expression). These are essentially functions that invoke immediately and return an object (or an interface, in other words), which then becomes the module.

Inside these functions are code that can be private in such ways that they'd only be accessible within that function's scope unless the returned object provides methods that can access them somehow. This returned object is public to the outside world.

Here's an image representation of what this looks like, for those of you who are better at understanding things in a visual perspective:

module pattern using immediately invoked function expressions in javascript

We will implement our own module using an IIFE. This allows us to assign the return value of an IIFE directly onto a variable so that we can use it just like a JavaScript module.

For example, lets pretend that we are creating an RPG game and the first thing we decided to do was create a sorceress class. In general role playing games, sorcerers are highly powerful beings that possess strong magical abilities like fire, wind, electricity, etc. They commonly possess telekinetic powers to pick and move things around with just their minds. In just about every RPG game, sorceresses usually cast spells or magic, so we'll keep this concept in context when we define the interface for the sorceress.

const sorceress = (function() {
  const sideEffects = {
    intervals: {},
  }

  function _fireBolt(target, customDamage) {
    target.hp -= customDamage !== undefined ? customDamage : 15
  }

  function _thunderBolt(target) {
    target.hp -= 15
  }

  function blizzard(target) {
    target.hp -= 15
  }

  function _applyThunderBoltSideEffects(
    target,
    { interval = 1000, timeout = 15000 } = {},
  ) {
    if (sideEffects.intervals[target.id]) {
      clearInterval(sideEffects.intervals[target.id])
    }

    sideEffects.intervals[target.id] = setInterval(() => {
      target.hp -= 1
    }, interval)

    setTimeout(() => {
      if (sideEffects.intervals[target.id]) {
        clearInterval(sideEffects.intervals[target.id])
      }
    }, timeout)
  }

  return {
    fireBolt(target, options) {
      if (options) {
        _fireBolt(target, options.customDamage)
      } else {
        _fireBolt(target)
      }
    },
    thunderBolt(target) {
      _thunderBolt(target)
      _applyThunderBoltSideEffects(target)
    },
    blizzard,
    castAll(target) {
      this.fireBolt(target)
      this.thunderBolt(target)
      this.blizzard(target)
    },
  }
})()

In this example, our sorceress class has four methods available to use from the outside world: sorceress.fireBolt, sorceress.thunderBolt, sorceress.blizzard, and sorceress.castAll.

Inside this module, we declared three private functions and four public functions. We can obviously tell that that the functions prefixed with underscores _ are the private functions while latter are public. We know this because the ones with the underscores aren't being returned--instead some of them are just being used by the public methods. This concept of being able to reference local variables by lexical scope is called closure. Because we did not return the variables prefixed by underscores they are not available outside of the module--but they can be if the returned methods chooses to make that happen if desired.

Having the power to declare private and public variables this way is what makes the module pattern arguably the most powerful design pattern in JavaScript apps. The same pattern is essentially what's being constantly used today when we import or require from nodejs modules as well as including <script> tags that point to a library like jQuery.

Global Import

Uses of the module pattern in JavaScript is not limited to our previous code example. The module pattern in JavaScipt is powerful thanks to JavaScript's flexibility in nature. For example, JavaScript has a feature known as implied globals. If it's used in an assignment, the global is created if it doesn't already exist. So when we use or create global variables in anonymous closures, things can actually become easier to our favor to an extent.

Unfortunately however, the resulting issue is that our code becomes harder to manage over time because it is not obvious to us knowing which variables are global in a given file.

Luckily, anonymous functions provide an easy alternative. By passing in globals as parameters to our anonymous functions, they get imported into our code, which is both faster and clearer than implied globals.

Here's a code example illustrating this concept:

const myModue = (function(_) {
  // do stuff
})(lodash)

As you can see, we now have access to lodash as part of the global variable _

Here's an image representing what this may look like now:

global importing variables to modules in the module pattern inside javascript

Why Modules in General?

In general, there are multiple benefits to using modules.

Here are the most important:

It helps you maintain your code better

By definiton, a module is self-contained and should not rely on the outside world to survive. Updating a single module should be as easy as possible and should not break another part of the app when changed. A well module should be aimed to be well constructed and lessen the dependencies on parts of your code as much as possible so that they are decoupled from other parts of your code.

If we take a look at our sorceress class in the previous code example, we can already assume that if we try to change one of the methods defined inside, debugging would end up becoming a stressful process if it proceeded to break other parts of the codebase, especially if it creates a domino effect. Modules should be carefully designed in a way that any changes to them in the future doesn't end up affecting other parts of the code.

It helps us to avoid polluting the global namespace

In the JavaScript language, all variables located outside the scope of top-level functions are global. This means that other code can access them. This is problematic because it creates a situation called namespace pollution, where any completely unrelated code shares variables in the global scope, which is something you want to avoid at all times.

It helps us to reuse code

If you've been developing in JavaScript for awhile, you've probably found yourself copy and pasting code to multiple projects. This is fine until you start realizing that you had written the code snippet using bad practices and decide to rewrite it using better practices. If you had copy and pasted the code to multiple projects you are faced with the boring, repetitive task of having to go change every copy of the code if you wanted them to be applied the new changes. It would certainly become much easier to have a module that can be reused over and over so that you only need to update the module at one location in which consumers of the module will automatically be reflected of the changes every time.

Conclusion

And that concludes the end of this post! I hope you found this to be valuable, and look out for more in the future!



© jsmanifest 2019