A crash course in Javascript modules and globals
November 22, 2011
Intro
Javascript has an awkward habit of allowing developers to define variables and functions in a global scope, when often this is not the intention it's just that they haven't been properly encapsulated. This can present some potential problems such as overwriting variables when you don"t mean to and not providing the seperation of functions and variables which you would expect/desire.
These issues are especially prevalent with a language such as Javascript which won't warn you about such problems. This can introduce bugs which are hard to track and make code hard to maintain.
This article aims to offer a method to write your Javascript methods in a modular fashion and avoid some of these pitfalls.
The problem
Presume we write a simple function to add two numbers and an initial value. Ignoring the fact that this method could be improved in many ways, our method and variable might look like this:
//define a variable in global scope
var globalInitialValue = 10;
//define a global function which uses the variable above
function globalAdder(a, b) {
return globalInitialValue + a + b;
}
In Javascript, functions and variables are automatically available to their calling scope, since we've not told it otherwise this variable and function are available globally since our calling scope is the global context.
In the case of a web application the global scope is the "window", using Firebug or any other debugging tool we can see the "window" object and note the presence of the variable "globalInitialValue" and the function "globalAdder" as shown in the images below
The problem with this is that we can execute the function directly and if we're not careful, overwrite the "globalInitialVariable" without meaning to. For the purposes of demonstration we will do this intentially, but it's purely a proof of concept to show hows variables and functions could be re-assigned without wanting to.
Note the console window below where we've executed our function to add 5 + 5, to our global initial variable and got the desired result "20", then re-assigned our global variable to 50 and executed the "globalAdder" function a second time and got a result we didn't expect.
The example above is trivial, but imagine what could happen if we accidentially let a commonly named variable, e.g. 'i' creep into the global scope without us meaning to.
Method
One method to resolve this is to encapsulate your Javascript functions inside objects which expose only the functions you wish to the global context. The code snippet below shows a simple example of this way of working:
//define a single global object which is publicly accessible
var ASingleGlobalObject = {};
//define a module in the object, also publicly accessible
ASingleGlobalObject.AddingModule = (function() {
//define a private object in the module to contain vars,
//functions we want to hide
var privateStuff = {
initialValue: 10
}
//define a public object which will will return from
//"AddingModule" anything in here will be available publicly
var publicFunctions = {
adder: function(a, b) {
return privateStuff.initialValue + a + b;
}
}
//return the public functions
return publicFunctions;
}()); //execute the function immediately so it"s accessible
There are several important pieces to this code. Firstly, we define an object within the global scope "var ASingleGlobalObject = {};" this will be our window into our methods and provides a wrapper to the rest of our code, it's effectively a global namespace.
In a production environment this might be named after your company or application. There are better ways of defining this object and checking for pre-existence, but I'm trying to keep things as simple as possible.
Secondly, we are defining a function "AddingModule", this is effectively a class within our namespace, within this we will create a function. This function is defined with parenthesis on the tail, meaning it will be executed immediately, this allows us to access the functions as soon as the script is executed and we don't have to explictly call it. We have enclosed this function itself in parenthesis purely as a stylistic indication that the function is to be treated in this way.
Thirdly, we defined two variables in the "AddingModule" function, "privateStuff" and "publicFunctions", these are both objects. The content of "publicFunctions" is returned from the "AddingModule" functions and will be available to the calling context (ASingleGlobalObject), the other variable "privateStuff" is NOT returned by the "AddingModule" function and as a result will not be visible.
The image below shows in firebug, that we can access our "ASingleGlobalObject" and it's contained function "AddingModule" and finally within that, our target function "adder"
The "privateStuff" object is the place we can locate functions and variables we want to use in the "AddingModule" functions, but don"t want to be visible outside. This allows us to contain our "initialValue" variable, free from being overwritten accidentally in the global context.
The "publicFunctions" object is returned when we execute the "AddingModule" function, this itself is executed immediately due to the parenthesis which means our public functions are available immediately too.
The "publicFunctions" object has a property which we've named "adder", and this property is itself a function. This function performs the simple addition operation we have used before, but it accesses the "initialValue" variable using "privateStuff.initialValue". Since this variable is not accessible outside its function scope/calling context, it is not accessible outside of the "AddingModule" which gives us a neat encapsulation of its value.
The image below shows how we can call the "ASingleGlobalObject.AddingModule.adder(5,5)" and get the expected result of "20" and also how we cannot access or alter the value of "initialValue" when we try in a couple of different ways.
Conclusion
The method above allows a mechanism to modularise our Javascript code to ensure that functions and variables are only defined and accessed from the scope they were intended. It also allows us to provide functions and variables which are totally invisible to calling code, preventing accidental access.
Being able to provide a solid scope for our code and hide variables we intend to be local explicitely within objects, should improve the scalability and robustness of our Javascript code, ensuring less hidden bugs improving code quality.
CAVEAT: I'm aware there are better ways to write the majority of this code, but as far as I'm aware the concept is sound. The snippets are shown as examples and should not be taken verbatim.