logo image Faisal Rahman
ID EN
My profile picture taken in the summer Faisal Rahman

IIFE-Based Modules


Good authors divide their books into chapters; good programmers divide their programs into modules.

Linguistically, a module can be defined as a self-contained component of a system that supports the program of that system [1]. A module, in programming, divides a program’s code into several parts grouped according to certain criteria. Dividing a program into modules has the same advantages as dividing a book into chapters: it defines a clear structure for a system.

With modules, parts of a program become easier to identify based on established criteria, and are often also given their own namespaces. Namespacing is important in Javascript because of the potential for naming conflicts in the global scope. In fact, it is strongly recommended to limit the use of global variables in Javascript to avoid polluting the global scope, which can harm the performance of our Javascript program [1:1].

Modularization in Javascript

Regarding the effort of namespacing in Javascript, the first thing we might think of is implementing a namespace with an object.

var Recipes = {
  riceAmount: "1 piring",
  soyAmount: "2 sendok makan",

  preheatOil: function () {
    console.log("Panaskan minyak.");
  },
  addRice: function () {
    console.log("Tambahkan " + this.riceAmount + " nasi.");
  },
  addSoySauce: function () {
    console.log("Tambahkan " + this.soyAmount + " kecap.");
  },

  friedRice: function () {
    this.preheatOil();
    this.addRice();
    this.addSoySauce();
  },
};

Recipes.friedRice(); // Panaskan minyak. Tambahkan 1 piring nasi. Tambahkan 2 sendok makan kecap.

With this object-based namespacing, we only need to store Recipes in the global scope, but the implication is that we can access the entire contents of Recipes from places where we shouldn’t.

Recipes.riceAmount = "2 kontainer";
Recipes.soyAmount = "1 ember";
Recipes.friedRice(); // Panaskan minyak. Tambahkan 2 kontainer nasi. Tambahkan 1 ember kecap.

Ideally, riceAmount and soyAmount should only be accessible by friedRice so as not to mess up the fried rice recipe defined at the start. The example above shows that namespacing using objects cannot yet satisfy the principle of least privilege [1:2]. To satisfy it, we must be able to make information other than the recipes in Recipe inaccessible from outside the module. In other words, we must make that information private to the ‘Recipes’ module. Creating private variables (also known as local variables) in Javascript is done by putting the code we want to hide inside a function.

Function Block Scoping

Scope restriction in Javascript is done based on function blocks. This means all variables defined inside a function’s block can only be accessed from within that function block itself.

function greet() {
  var greeting = "Hello!";
  console.log(greeting);
}

greet(); // Hello!
console.log(greeting); // ReferenceError: greet is not defined

In this code, we successfully made greeting a private variable for the greet function. Now let’s apply the same technique to the Recipes module.

function Recipes() {
  var riceAmount = "1 piring";
  var soyAmount = "2 sendok makan";

  function preheatOil() {
    console.log("Panaskan minyak.");
  }

  function addRice() {
    console.log("Tambahkan " + riceAmount + " nasi.");
  }

  function addSoySauce() {
    console.log("Tambahkan " + soyAmount + " kecap.");
  }

  function friedRice() {
    preheatOil();
    addRice();
    addSoySauce();
  }

  friedRice();
}

Recipes(); // Panaskan minyak. Tambahkan 1 piring nasi. Tambahkan 2 sendok makan kecap.
console.log(riceAmount); // ReferenceError: riceAmount is not defined
console.log(soyAmount); // ReferenceError: soyAmount is not defined

In the code above, it can be demonstrated that riceAmount and soyAmount are now private variables to Recipes.

Closure

In the last code example, the Recipes function provides functionality by executing its internal functions. The implication of this model is that the function will be done after it is called (Recipes()), so it cannot store state — a capability that a module needs.

To demonstrate this, I will change the friedRice functionality into a counter that counts how many times fried rice has been cooked, with the help of a new variable counter.

function Recipes() {
  var counter = 0;

  function count() {
    count++;
  }

  function friedRice() {
    console.log("Nasi goreng telah dimasak" + counter + "kali");
  }

  friedRice();
}

Recipes(); // Nasi goreng telah dimasak 1 kali
Recipes(); // Nasi goreng telah dimasak 1 kali
Recipes(); // Nasi goreng telah dimasak 1 kali

With the current model, Recipes cannot store state because once its function execution is complete, the function is immediately cleaned up by the garbage collector. When we need to be able to store state variables inside a function, we need to make use of closure.

A closure is when a function can remember and access its scope even when the information inside that scope is called from outside the scope itself [1:3]. Let’s modify our last Recipes module so that it returns the friedRice function instead of running it.

function Recipes() {
  var riceAmount = "1 piring";
  var soyAmount = "2 sendok makan";

  function preheatOil() {
    console.log("Panaskan minyak.");
  }

  function addRice() {
    console.log("Tambahkan " + riceAmount + " nasi.");
  }

  function addSoySauce() {
    console.log("Tambahkan " + soyAmount + " kecap.");
  }

  function friedRice() {
    preheatOil();
    addRice();
    addSoySauce();
  }

  return friedRice;
}

var foo = Recipes();
foo(); // Panaskan minyak. Tambahkan 1 piring nasi. Tambahkan 2 sendok makan kecap.

In this code, once the Recipes function has finished running and is stored in the variable foo, our intuition might assume the variables and functions inside the Recipes instance that just ran would be immediately deleted from memory by the garbage collector. However, as we can see when foo() is executed, all those variables and functions are still in memory and working fine.

With closure, we can effectively store state in local/private variables and provide a flexible interface through a function’s return value.

The Module Pattern with IIFE

In the last code example, our Recipes function has applied privacy to its internal logic, and only provides an interface in the form of its return value — the friedRice function — for printing the fried rice recipe. However, since the Recipes module is fundamentally intended to be a repository of recipes for various foods, we need to change its design to accommodate more than just friedRice.

Say I want my program to be able to give the user a fried noodles recipe; the most logical thing to do is to add friedNoodles functionality to the Recipes module. We can achieve this given two things: 1) a function can return an object as its return value; 2) with closure in effect, the object returned by the function can remember and access its private variables.

function Recipes() {
  var riceAmount = "1 piring";
  var noodleAmount = "1 mangkok";
  var soyAmount = "2 sendok makan";

  function preheatOil() {
    console.log("Panaskan minyak.");
  }

  function addRice() {
    console.log("Tambahkan " + riceAmount + " nasi.");
  }

  function addNoodles() {
    console.log("Tambahkan " + noodleAmount + " mie.");
  }

  function addSoySauce() {
    console.log("Tambahkan " + soyAmount + " kecap.");
  }

  return {
    friedRice: function () {
      preheatOil();
      addRice();
      addSoySauce();
    },
    friedNoodles: function () {
      preheatOil();
      addNoodles();
      addSoySauce();
    },
  };
}

var recipes = Recipes();
recipes.friedRice(); // Panaskan minyak. Tambahkan 1 piring nasi. Tambahkan 2 sendok makan kecap.
recipes.friedNoodles(); // Panaskan minyak. Tambahkan 1 mangkok mie. Tambahkan 2 sendok makan kecap.

With the ability to provide such a complete interface as shown above, our Recipes function is effectively already a module. However, there is still one problem as can be seen above: we are using two variables in the global scopeRecipes, which is a consequence of the function declaration (function Recipes() { /*...*/ }), and recipes, created to hold Recipes so it can be called.

This is where we can use an Immediately-Invoked Function Expression (IIFE) to cut down on global variable usage. We can turn the Recipes function declaration into an IIFE by wrapping it in () like this.

(function Recipes() {
  /* ... */
})();

That module uses 0 global variables. The problem is we then have no reference to the module, making it useless. In conclusion, to be a complete module, we need to store the IIFE in a variable for reference purposes.

/*
 ** NOTE: the parentheses around the function declaration
 ** Recipes are actually optional, but it is recommended
 ** to still write them for readability
 ** see footnote #5 for more details
 */
var recipes = (function Recipes() {
  /* ... */
})();

recipes.friedRice();
recipes.friedNoodles();

And there we have an IIFE-based module that can be used throughout our program via the object interface it returns.



  1. Immediately-Invoked Function Expression dan Motivasinya ↩︎ ↩︎ ↩︎ ↩︎