Modules in JavaScript – Global Variable – A complete understanding

Modules

Private members

Because JavaScript has no syntax to denote scope, we need to use closures to implement private members. An extended example of this can be seen in my post about private variables in JavaScript
A simple example of this is shown in the following snippet:

1
2
3
4
5
6
7
8
9
10
MYAPPLICATION.MODEL.PRODUCTS.product = function(price){
    var price = price;   
    
    return {
        isVatApplicable: true,
        getPrice: function(){                                              
            return price;                                           
        }
    };
};

In this snippet, the variable price is private, but it is accessible to the method because it’s inside the closure. The public members are isVatApplicable and getPrice.

Another pattern would be the revealing module. It’s essentially the same as the previous pattern, but now we declare everything privately and then decide what we return (and thus expose as public members):

1
2
3
4
5
6
7
8
9
10
11
12
MYAPPLICATION.MODEL.PRODUCTS.product = function(price){
    var price = price;   
    var isVatApplicable = true;
    var getPrice: function(){                                              
            return price;                                           
        };
    return {
        isVatApplicable: isVatApplicable,
        getPrice: getPrice
    };
};

To me this last pattern is the clearest, because you are declaring everything up front and then explicitly make some members public. It shows the intent.

Private members: caveats

There’s one caveat with the pattern described above. Since in JavaScript all variables are passed by reference you could potentially expose members that you didn’t want to be public. In the example above that doesn’t happen because price is a value.
However, let’s consider the following example:

1
2
3
4
5
6
7
8
9
10
11
12
MYAPPLICATION.MODEL.PRODUCTS.product = function(width, height){
    var dimensions = {
        width: width,
        height: height
    };
    var getDimensions = function(){                                              
            return dimensions;                                           
        };
    return {
        getDimensions: getDimensions
    };
};

We are following the same pattern here, so one might think that the dimensions-variable is private. The following code shows a caveat though:

1
2
3
4
5
6
7
var model = MYAPPLICATION.MODEL.PRODUCTS;
var p = new model.product(50,100);
var dims = p.getDimensions();
dims.width = 1000;
dims.height = 1000;
// alerts 1000 1000 => unexpected
alert(p.getDimensions().width + “ “ + p.getDimensions().height); 

This code will actually alert “1000 1000”. Because the dimensions variable is returned by reference the first time we call p.getDimensions, changing its values will affect the values of the private variable.

What solutions are there for this problem?
There are a few things you can do to mitigate this problem:

  • Do not return objects, but only actual values. In our example this would constitute of creating two methods: getWidthand getHeight.
  • Create a copy of the object. You could do this in the getDimensions method.

Tying it together

The following example ties all the previous techniques together:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// create the root namespace and making sure we're not overwriting it
var MYAPPLICATION = MYAPPLICATION || {};
// create a general purpose namespace method
// this will allow us to create namespace a bit easier
MYAPPLICATION.createNS = function (namespace) {
    var nsparts = namespace.split(".");
    var parent = MYAPPLICATION;
    // we want to be able to include or exclude the root namespace
    // So we strip it if it's in the namespace
    if (nsparts[0] === "MYAPPLICATION") {
        nsparts = nsparts.slice(1);
    }
    // loop through the parts and create
    // a nested namespace if necessary
    for (var i = 0; i < nsparts.length; i++) {
        var partname = nsparts[i];
        // check if the current parent already has
        // the namespace declared, if not create it
        if (typeof parent[partname] === "undefined") {
            parent[partname] = {};
        }
        // get a reference to the deepest element
        // in the hierarchy so far
        parent = parent[partname];
    }
    // the parent is now completely constructed
    // with empty namespaces and can be used.
    return parent;
};
// Create the namespace for products
MYAPPLICATION.createNS("MYAPPLICATION.MODEL.PRODUCTS");
MYAPPLICATION.MODEL.PRODUCTS.product = function(width, height){
    // private variables
    var dimensions = {
        width: width,
        height: height
    };
    // private methods
    // creating getWidth and getHeight
    // to prevent access by reference to dimensions
    var getWidth = function(){
        return dimensions.width;
    };
    var getHeight = function(){
        return dimensions.height;
    };
    // public API
    return {
        getWidth: getWidth,
        getHeight: getHeight
    };
};
// Create the namespace for the logic
MYAPPLICATION.createNS("MYAPPLICATION.LOGIC.BUSINESS");
MYAPPLICATION.LOGIC.BUSINESS.createAndAlertProduct = function () {
    var model = MYAPPLICATION.MODEL.PRODUCTS;
    var p = new model.product(50,100);
    alert(p.getWidth() + " " + p.getHeight());
};

As you can see, this patterns allows for modular and well-structured JavaScript. Using these techniques can make your code easier to read and to modify.
There are other techniques available too, and you can build and vary this pattern. In my next post I will show you can create a sandbox. This will allow your code to operate in an isolated environment. It also allows for a type of dependency Injection.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s