Ronaldo Vitto Lewerissa

Software engineering learning documentation.

JavaScript Module Pattern

Modules are needed in JavaScript to achieve modularity, which is to keep pieces of code that is unrelated to be independent from one another in a loosely coupled way (changes in one affects little to no effects to another).

These sort of module patterns are popular when module bundler like Webpack is still not a thing.

Modularity is considered by many to be well-structured code.

It's the idea of separation of concern. Revolves around the principle of high cohesion and low coupling.

Module provides encapsulation and avoid namespace collision. It acts as a replacement of class.

Encapsulation is something that JavaScript natively doesn't have. To counter this, we use something called closure. It provides private variable/method.

To sum that:

  • provide encapsulation

  • support modularity

  • avoid namespace collision

Module design pattern uses something referred to as IIFE (immediately invoked function expression), it's simply a function that wraps inside of a parentheses.

let iife = (function () {  
  ...
})();

or,

let iife = (function () {  
  ...
}());

The parentheses tells JavaScript engine that what's inside has to be an expression, therefore it knows when it encounters a function, the function has to be a function expression, not a function declaration.

There is also an important feature JavaScript has that you need to know, it is known as implied globals.

On a non global execution context, let's say something like this:

function foo () {  
  undeclaredVar = 3;
}
foo();  
console.log(undeclaredVar); // outputs 3  

Although undeclaredVar has not been declared prior, it still works!

JavaScript engine will check whether undeclaredVar is available in the current execution context, if not, then it will check it's parent execution context and see if it's there. If it's fruitless, then JavaScript engine will treats it as a global variable (freshly created with the corresponding assignment as the value).

Bear in mind that implied globals will not work in the global execution itself on strict mode!

a = 3; // throws error  
Revealing Module Pattern

This is the basic module pattern you'll use and often see to create a module. You probably saw it in another types of pattern (not just module pattern) such as factory.

let awesomeModule = (function () {  
  // private variable goes here
  return {
    // public variable goes here
  };
})()
Privilege Member

Privilege members are used to access private members indirectly. It's like a setter and getter. An advantage is to be able to filter and protect private members for being accidentally altered with an unexpected value or solely errors.

let awesomeModule = (function () {  
  let numFoods = 10;
  return {
    setFood(numFood) {
      if (typeof numFood !== 'number') {
        throw Error('setFood require number parameter');
      }
    }
  };
})()
Augmentation

This is a module pattern used to add additional properties to our current module. It is divided into two distinct variance:

  • Loose augmentation

  • Strict augmentation

Tight augmentation requires module to load synchronously (in order), while strict augmentation does not.

Tight Augmentation:
let module = (function (module) {  
  module.newProp = 'bar';
  module.newMethod = () => {};
  return module;
})(module)

So it is no longer creating but instead adding.

Notice that is passes module, therefore module needs to already been initialized (otherwise it will be undefined).

I don't really know the importance of returning and reassigning the module on this pattern. I guess it's purpose is generally consistency.

Loose Augmentation:
let module = (function (module) {  
  module.newProp = 'bar';
  module.newMethod = () => {};
  return module;
})(module || {})

Loose augmentation does not requires you to provide an existing module.

Now in this case, it is important to return the module and reassign it, just in case that the module does not exist in the first place. Otherwise, no augmenting will ever takes place.

As for the expression module || {} it will returns {} when module is falsy. You can think {} as a default.

The only downside is incapability to overwrite methods.

Sub-Module

Nothing special here, creating a sub-module is as simple as creating a module:

module.subModule = (() => {  
  return {};
})()

Written by Ronaldo Vitto Lewerissa

Read more posts by this author.