Samuel Sjöberg's weblog

Skip to navigation

Variable encapsulation in Javascript

This summer has meant a lot of javascript development for me. I developed an interactive mockup, empowered with a lot of javascript, and now is the time to share some ideas and solutions I've come up with.

When writing a lot of javascript that needs to work together it becomes important to control the namespace of your functions. To avoid conflicts and avoiding long cumbersome variable names, encapsulation is used.

When encapsulating variables, we take one step closer to an OO approach. The main difference from traditional OO and the one I will show here is that we're treating instances of objects rather than classes.

There are as many ways of writing OO javascript as there are programmers so I'm not going to talk about that here, just search for it if interested. One thing that needs to be mentioned however, is prototypes.

Prototypes

A prototype is a property of an object that is visible outside the object, i.e. a public property. String.substring is an example of a built in prototype. To define our own prototypes, we do the following.

String.prototype.trim = function() {
   return this.replace(/^\s+|\s+$/g, "");
}

And then, magic... When a prototype is defined, it is instantly accessible for all objects of that type, no matter if they are defined before or after the prototype was added to the object! This means that we can use prototypes to write OO javascript.

I mainly use prototypes as in the example above. Whenever I miss a property of a common object, e.g. String or Array, I add it as a prototype.

Keep it secret, keep it safe

The way I've come to write my scripts lately is heavily influenced by the style found in the sIFR RC4 release. A function is defined with all the code needed nested inside of it. Then the function is executed and thus exposed as a variable that the nested functions can be accessed through. An example is in place...

var Queue = function() {
   var items = new Array();
 
   function Queue() {};
 
   Queue.add = function(item) {
      items[items.length] = item;
   }
 
   Queue.next = function() {
      return items.shift();
   }
 
   return Queue;
}();
 
// Example of usage
 
Queue.add('Samuel');
Queue.add('Lisa');
alert(Queue.next());
alert(Queue.next());

Now, let's go through the script line by line to see what actually happens.

var Queue = function() {

The first thing needed is a variable to access and encapsulate the code with. Javascript's variable scope encapsulate all functions inside of Queue, i.e. they can only be accessed through Queue. Queue is the only variable that will be visible in the global javascript scope. This, in turn, means that Queue is the only variable name that has to be unique.

   var items = new Array()

This line defines a private variable items which only is accessible to other members of Queue. items is hidden from functions outside of Queue, thus avoiding name conflicts and long, hard-to-remember variable names.

   function Queue() {}

Think of this as the constructor, a function with the same name as the encapsulating variable.

   Queue.add = function(item) {
      items[items.length] = item;
   }

On this line, a function is attached to the constructed object. When a variable is defined as Queue.variableName it becomes public and can be accessed from outside the object, e.g. the add function is accessed as Queue.add('myItem'). To define private functions, don't attach them to the constructed object, function privateAdd(item) { ... }.

So what do this function do? It simply adds the item to the end of the internal queue named items.

   Queue.next = function() {
      return items.shift();
   }

This is another public function. It removes the first item in the queue and returns it. Array.shift() is a standard operation on arrays.

   return Queue;
}();

To make the public variables available, the internal version of Queue must be returned and assigned to the global version of Queue. This is what happens on the last line. After the execution, all properties assigned to Queue can be accessed through Queue from the global scope.

Climbing the DOM three

If one want to access the DOM, one must keep in mind that the structure doesn't exist until the document is fully loaded. Because of this, execution is often performed in window.onload. However, there are alternatives that can be used.

One way is to actually assign window.onload inside of our encapsulation object. Just remember to preserve any functions that has already been assigned to window.onload.

Another, faster way that I learned from the above mentioned sIFR release, is to call the script at the end of the HTML document. At the end of the document, the DOM three has been generated, but images and embedded objets have not fully loaded yet. However, we don't have to wait for them unless the script concerns them, therefore the script can be initiated here. I usually define a Object.init() function that I call from the end of the document. In init all the required setup of events etc. is performed.

Imagine that we have the following init function in Queue that puts all items in a list into the queue.

Queue.init = function() {
   var items = document.getElementById('queue')
            .getElementsByTagName('li');
   for (i = 0, l = items.length; i > l; i++)
      add(list[i].firstChild.nodeValue);
}

The following snippet, from the end of the HTML document is then used to execute init and set up the queue.

<script type="text/javascript">
//<![CDATA[
if (Queue != undefined) Queue.init();
//]]>
</script>
</body>
</html>

This is the way I do things, at least at the moment. vForm, for example, is written is this way (except that it uses window.onload) and I will post more things shortly that also uses this approach.

Pages linking to this entry

Pingback is enabled on all archived entries. Read more about pingback in the Pingback 1.0 Specification.

About this post

Created 31st July 2005 13:46 CET. Filed under Javascript and DOM.

0 Comments
0 Pingbacks