JS Modules


Published: 2014-10-24
Updated: 2014-10-25
Web: https://fritzthecat-blog.blogspot.com/2014/10/js-module-encapsulation.html


Encapsulation in object-oriented programming languages always was a much disputed topic:

From my point of view the answer is

No, it would be an unforgiving and unpredictable world, where the wing beat of a butterfly on one side could cause a hurricane on the other side.
It's all about complexity, and how to border it. Complexity is like the water of a river after ten days of rain - it sweeps away everything.

Imagine many small problem solutions, forming the solution of a bigger problem, which again is just one of many solutions for an even bigger problem. Now the solution for the top problem doesn't work any more, and you must find the error.
Imagine everything in the source code is public. The error could be everywhere, because the programmers could have called everything from everywhere. But when they have separated public from private methods, the possible hazards are significantly less.

So what you need to do as software developer is

  1. solve a problem
  2. put the solution into a capsule, leaving open just the necessary access points (hiding complexity increases usability)
  3. use it, together with other such capsules, to build the solution for a bigger problem
When looking for an error then, you will not be confronted with the full complexity of the system, you will search only along the public ways until you find the malfunctioning capsule, only there you have to go into private details.

Remember:
Hide as much complexity as possible,
don't confront the outside world with complex details.

When JavaScript programmers talk about modules, they mostly mean objects with properties and functions that have access to nested variables and functions, the latter not being accessible from outside.
But JS does not have access modifiers. How can we achieve encapsulation?

Encapsulation Archetype

When you want to write some JS code without polluting the global namespace with variables and functions, you can do the following:

(function() {
console.log("I am executing!");
}());

This is called a self-executing function. This kind of suicide is caused by the terminating parentheses (:-).
The comprehensive parentheses around the whole function definition are needed to be syntactically correct. But it doesn't seem to matter if they are just around the function definition or around all code, the following also works:

(function() {
console.log("I am also executing!");
})();

You can also parameterize this capsule:

(function(what) {
console.log("I am "+what+"!");
}("doing whatever you want"));

This outputs:


I am doing whatever you want!

Whatever you write inside the self-executing function will not "pollute" the global namespace. And it will be executed as soon as the JS interpreter encounters the terminating parentheses. So this represents an encapsulation archetype.

Module Archetype

Lets go JS-extreme and try a private function and variable:

var printer = (function() {
var defaultWhat = "Default Hello World";

function print(what) {
console.log(what || defaultWhat);
}

return {
print: print
};
})();

printer.print();
printer.print("Hello World");

When called, this outputs:


Default Hello World
Hello World

This is called the "Revealing Module Pattern", because it solves problems in a private hidden space, and finally exports some public access points in the returned object.

But wait, here again we "polluted" the global namespace with a variable printer, holding a singleton object. Can't we do better?

Namespace Archetype

We can hide all our upcoming variables into our own namespace. However, we always must reserve a name for our namespace when it needs to be globally available.

A JS-namespace is an object, possibly holding other namespace objects. This might lead to a hierarchy of namespaces, built using the following pattern:

var my = my || {};
my.module = my.module || {};
my.module.printer = my.module.printer || {
print: function(what) {
console.log(what);
}
};

my.module.printer.print("Hi there");

Basically this reads as

"let variable 'my' be either the already existing 'my', or a new object when not yet defined."
So the object is created only the first time the JS interpreter executes this line.
We also could have written this like the following (with an ugly amount of indentation):

var my = my || {
module: {
printer: {
print: function(what) {
console.log(what);
}
}
}
};

my.module.printer.print("Hi there");

But mind that this is not exactly the same. When my already existed at this point, the module and printer objects would not have been written into the namespace! So better do it in the way shown before.

We could now drop properties/functions in the my space, or in the my.module space. Mind that everything in an object is visible and mutable from outside (public). The namespace is not a capsule, it is just a convenience, using long dotted names instead of simple names that once could get ambiguous.

Namespaced Module Instances

How about a function that dynamically creates new printer instances which can have a private state?

var my = my || {};
my.module = my.module || {};

my.module.createPrinter = my.module.createPrinter || function(toPrint) {
var running = false;

var on = function() {
running = true;
console.log("Ra ta ta .... ");
};
var off = function() {
console.log("... ta ta: "+toPrint);
running = false;
};
var state = function() {
return running;
};

return {
start: on,
stop: off,
isRunning: state
};
};

There is a private state in any created printer, represented by the running variable, and by the toPrint parameter. The latter is captured by the function closure and thus is available inside the function like a variable.

Mind that you shouldn't implement any function in the return-object. Just return references to the private functions inside the module capsule.

Here is some test code for this printer factory function:

var myFirstPrinter = my.module.createPrinter("First Hello World!");
console.log("myFirstPrinter.isRunning(): "+myFirstPrinter.isRunning());
myFirstPrinter.start();
console.log("myFirstPrinter.isRunning(): "+myFirstPrinter.isRunning());

var mySecondPrinter = my.module.createPrinter("Second Hello World!");
console.log("mySecondPrinter.isRunning(): "+mySecondPrinter.isRunning());
mySecondPrinter.start();
console.log("mySecondPrinter.isRunning(): "+mySecondPrinter.isRunning());

myFirstPrinter.stop();
console.log("myFirstPrinter.isRunning(): "+myFirstPrinter.isRunning());
console.log("mySecondPrinter.isRunning(): "+mySecondPrinter.isRunning());

mySecondPrinter.stop();
console.log("myFirstPrinter.isRunning(): "+myFirstPrinter.isRunning());
console.log("mySecondPrinter.isRunning(): "+mySecondPrinter.isRunning());

This outputs:


myFirstPrinter.isRunning(): false
Ra ta ta ....
myFirstPrinter.isRunning(): true
mySecondPrinter.isRunning(): false
Ra ta ta ....
mySecondPrinter.isRunning(): true
... ta ta: First Hello World!
myFirstPrinter.isRunning(): false
mySecondPrinter.isRunning(): true
... ta ta: Second Hello World!
myFirstPrinter.isRunning(): false
mySecondPrinter.isRunning(): false

As we see each printer has its own private state, not intervening with the states of other printer instances.

Module Instantiation with Inheritance

We can create module singletons, we can instantiate modules, now we want to instantiate modules that inherit from other modules!

I strongly advice to use functional inheritance, it is simple and transparent.
What we always need for functional inheritance is a function that can copy a super-object, to let reuse the functionality implemented there. Here is a simple utility to do that:

var shallowCopy = function(source) {
if (source instanceof Function || ! (source instanceof Object))
throw "shallowCopy() expected Object but got "+source;

var target = {};
var propertyName;

for (propertyName in source)
if (source.hasOwnProperty(propertyName))
target[propertyName] = source[propertyName];

return target;
}

When you have jQuery in your web page, you can use jQuery.extend() instead of this. jQuery is for browser engines what Java is for operating systems, so use it wherever you can.

We can derive the printer instance to be a suspendable printer:

my.module.createSuspendablePrinter = my.module.createSuspendablePrinter || function(toPrint) {
var printer = my.module.createPrinter(toPrint); // define super-object
var base = shallowCopy(printer); // save old implementations
var suspended = false;

printer.suspend = function() {
suspended = true;
console.log("... suspending ...");
};
printer.resume = function() {
console.log("... resuming ...");
suspended = false;
};
printer.isRunning = function() { // override
if (base.isRunning() && suspended) // call super
return false;
return base.isRunning();
};

return printer;
};

These test lines

var mySuspendablePrinter = my.module.createSuspendablePrinter("Hello Suspendable World!");
mySuspendablePrinter.start();
console.log("Started mySuspendablePrinter.isRunning(): "+mySuspendablePrinter.isRunning());
mySuspendablePrinter.suspend();
console.log("Suspended mySuspendablePrinter.isRunning(): "+mySuspendablePrinter.isRunning());
mySuspendablePrinter.resume();
console.log("Resumed mySuspendablePrinter.isRunning(): "+mySuspendablePrinter.isRunning());
mySuspendablePrinter.stop();
console.log("Stopped mySuspendablePrinter.isRunning(): "+mySuspendablePrinter.isRunning());

output the following


Ra ta ta ....
Started mySuspendablePrinter.isRunning(): true
... suspending ...
Suspended mySuspendablePrinter.isRunning(): false
... resuming ...
Resumed mySuspendablePrinter.isRunning(): true
... ta ta: Hello Suspendable World!
Stopped mySuspendablePrinter.isRunning(): false

Hm, this printer is more a noise machine than something useful ... one more bad example :-(





ɔ⃝ Fritz Ritzberger, 2014-10-24