04 December 2010

Javascript/ECMAscript objects and namespaces

There seems to be quite a bit of confusion as to what these beasts are and what they're for. Hopefully, this article will clear up some of that confusion, although I certainly do not profess to be a real expert on any of the subjects herein and have simplified things before writing them down here.

Reading up on the concepts dealt with here and implementing them was in fact prompted by a real-world problem that I came up against at work. Part of my work involves maintaining a control panel site, each page of which can contain multiple "components". Each component comprises three elements most of the time: a PHP template file that handles the display, a PHP class that manages the mechanics of the back-end (often invoked via an AJAX call) and, given that AJAX obviously involves JavaScript, an associated JavaScript file containing data and functions that tie the display and the back-end together. Different components are written/maintained by different members of the development team and any single page can therefore contain components written by several developers. This situation can lead to clashes such as more than one JavaScript file containing functions or variables with the same name if we're not all very careful.

Namespaces and objects provide a tidy way to clean up the global namespace. If each component on the page behaves as a self-contained entity then each developer can use the names he or she wants for the elements making up his/her JavaScript code without treading on anyone else's toes in the process. Each developer's work is tucked away in its own namespace or object and can't interfere with any other developer's work.

So, how do we go about this tidy stuff?

Firstly, let's look at the namespace approach.

Consider this declaration of a generic object with a single property in JavaScript and an alert() that will display that property:

var myObject = { aNumber: 10 };
alert(myObject.aNumber);

A namespace is merely an extension of this concept in that we're also adding functions to the object myObject. Without them, the whole namespace thing is pretty pointless.

Let's add a function to myObject that displays the value aNumber and add some indentation to make reading this stuff easier:

var myObject = {
aNumber: 10,
display: function() { alert(this.aNumber); }
};

In order to get this working, you need to call the function called display within the myObject namespace:

myObject.display();

Note the use of the reserved word this used to refer to the property aNumber belonging to the object whose display method is being called.

This example is obviously somewhat lame in that it is making a bit of a mountain out of a molehill. Using a namespace just to display an alert box is overkill, so let's get a bit more adventurous and create a simple guessing game. This should do the trick.

First, the HTML for the page that is going to run the game:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">

<head>
<title>JavaScript guessing game</title>
<meta http-equiv="Content-Type" content="text/html" />
<script type="text/javascript" src="guessinggame.js"></script>
</head>

<body>

<input type="text" id="guess" value="" size="3" />
<input type="button" value="guess" onclick="guessingGame.play()" />
<input type="button" id="startbutton" value="start game" onclick="guessingGame.start()" />
<input type="button" value="cheat!" onclick="guessingGame.cheat()" />

</body>

</html>

Note that the text input box has been given the id guess and the button to start the game has been given the id startbutton. The text input and button are going to be accessed by the JavaScript.

And now the JavaScript file, guessinggame.js:

var guessingGame = {

initialised: false,

found: false,

numToGuess: 0,

start: function() {
this.numToGuess = 1 + Math.floor( Math.random() * 10 );
this.initialised = true;
this.found = false;
document.getElementById("startbutton").disabled = true;
},

cheat: function() {
if ( !this.initialised )
alert("The game hasn't started so you can't cheat yet!");
else if ( this.found )
alert("You've already found the answer, so why cheat now?");
else {
alert("The number to guess was: " + this.numToGuess);
this.found = true;
document.getElementById("startbutton").disabled = false;
}
},

play: function() {
if ( !this.initialised )
alert("The game hasn't yet started. Start one before guessing");
else if ( this.found )
alert("The game is over, you already got the right number!");
else {
guess = parseInt(document.getElementById("guess").value);
if ( guess == this.numToGuess ) {
this.found = true;
document.getElementById("startbutton").disabled = false;
alert("Correct! The number was indeed " + guess);
}
else
alert("Nope! Try again.");
}
}

};

Our namespace, guessingGame contains three properties: initialised, found and numToGuess. Note that all of these properties have to have something assigned to them when they are declared in the namespace. Unlike normal variables (or properties of an object declared as we'll see later on) they can't just be declared and have something assigned to them later on. Our namespace also contains three functions: start, cheat and play. How it all works should be pretty self-explanatory and is not the point of this article. What is the point is the fact that despite there being six "things" to play with here, there is only ONE thing in the global namespace: guessingGame. This means that someone else can come along and write a similar game in a namespace containing similarly-named properties and functions, but as long as that new game's namespace has a different name, there will be no clashes whatsoever. Both can cohabit on the same page without any risk of confusion.

While this is a neat way to prevent namespace collisions, we can do much better. Just like with any other form of object literal, anyone from inside or outside the object can access any of the data held within the namespace. If we opt for another approach, we can create objects that only expose to the outside world properties and methods that they want to make visible.

Let's rewrite the guessing game, but alter it so that we only have 5 guesses before the game is considered to be over and the player to have failed. We will therefore have to add a "guessesLeft" property, set it to 5 when the game starts and decrement it each time the player makes a guess. Once it reaches zero, the game is over. If we go for the traditional namespace as above, it would be trivial for the player to obtain unlimited guesses by setting the "guessesLeft" property to a positive number before calling the play function because that property is exposed to the outside world. This is how to avoid that situation using private properties.

Consider this declaration of a very simple object:

var simpleObject = function() {
var aNumber = 10;
};

var myObject = new simpleObject();

The first three lines define the class of our to-be object and give a name to that structure: simpleObject. The function defined in this way also acts as the constructor of that class. The last line actually instantiates that class. The object myObject is now an instance of the simpleObject class.

If you now try to display the property aNumber using something like this:

alert(myObject.aNumber);

All you'll get back is the response "undefined".

Why?

Because aNumber is a private property of the object. It can only be seen from within that instance of the object (or within a so-called "privileged" method that we'll be dealing with later). If you want to be able to use a property of an object, it must be a public property:

var simpleObject = function() {
this.aNumber = 10;
};

var myObject = new simpleObject();
alert(myObject.aNumber);

This time you get the expected result.

Rather than access the public property directly, let's now create a method of the simpleObject called "disp" that we can call and that will display the value of that property:

var simpleObject = function() {

this.aNumber = 10;

function disp() {
alert(this.aNumber);
}

}

var myObject = new simpleObject();

myObject.disp();

Uh oh... That didn't work! Why? Because "disp" is a private method and as such is not visible from the outside world! We need to create "disp" as a public method if we want to be able to invoke it from the global namespace. There are two ways of doing this. The first way is simply to add the method to myObject, our instance of the simpleObject class:

var simpleObject = function() {
this.aNumber = 10;
};

var myObject = new simpleObject();
myObject.disp = function() { alert(this.aNumber); };

myObject.disp();

The other way is to add the method to the definition of the class itself by adding it to the "prototype" property of that class:

var simpleObject = function() {
this.aNumber = 10;
};

var myObject = new simpleObject();
simpleObject.prototype.disp = function() { alert(this.aNumber); };

myObject.disp();

The first way is the "quick and dirty" way of doing it and is just as effective as the second way unless you have created multiple instances of the class and/or you have created other classes that build upon your base class simpleObject. If this is the case then, with the first method, you have to add the method to each instance, which can be laborious, not to mention inefficient with the method being stored as many times as there are instances. With the second solution, by adding the method to the class prototype, it immediately becomes available to all instances of the class and of classes derived from it. To boot, it is only stored once, which is more in line with the whole philosophy of object-oriented programming, re-use of code and all that.

Let's change our class slightly to add a private property and try and access it using a public method:

var simpleObject = function() {
var privatenum = 20;
}

var myObject = new simpleObject();
myObject.disp = function() { alert(this.privateNum); };

Uh oh... That didn't work either! Why? Because public methods can only access public properties, not private properties.

Enter the privileged method:

var simpleObject = function() {

var privatePr = 'private property';
this.publicPr = 'public property';

this.disp = function() {
alert(privatePr + " / " + this.publicPr);
}

}

var myObject = new simpleObject();
myObject.disp();

That's better! This shows that a privileged method can access not only public properties of an object but also that object's private properties.

So far we've seen private and public properties and we've accessed them using public and privilieged methods. One thing we've not yet been able to use is a private method.

Just like private properties, private methods can be used by privileged methods (and by other private methods). This is useful if, for example, there are multiple privileged methods that perform similar operations. Whatever is common to all of the privileged methods can be stored in a private method that is invoked by each of them.

It is now time to rewrite the guessing game, but this time using an object and building in the limited number of guesses. You will not need to change the HTML of the previous guessing game, only the JavaScript:

var gameObject = function() {

var guessesLeft;
var found;
var numToGuess;
var yourGuess;
var initialised = false;

this.start = function() {
guessesLeft = 5;
found = false;
numToGuess = 1 + Math.floor( Math.random() * 10 );
document.getElementById("startbutton").disabled = true;
initialised = true;
}

this.cheat = function() {
if ( !initialised )
alert("You can't cheat before even starting the game!");
else if ( found )
alert("You already found the number, why cheat now?");
else {
alert("The number to guess was: " + numToGuess);
gameOver();
}
}

this.play = function() {
if ( !initialised )
alert("Start a game before guessing!");
else if ( found )
alert("You already guessed the number, the game is over!");
else if ( guessesLeft > 0 ) {
yourGuess = parseInt(document.getElementById("guess").value);
if ( yourGuess == numToGuess ) {
alert("Congratulations! You guessed that the secret number was "
+ numToGuess + " with " + --guessesLeft + " guess(es) left.");
gameOver();
}
else {
if ( --guessesLeft > 0 )
alert("Nope. Try again. You have " + guessesLeft + " guess(es) left.");
else {
alert("Nope. You're out of guesses!");
gameOver();
}
document.getElementById("guess").value = '';
}
}
else alert("Sorry, you've already run out of guesses!");
}

function gameOver() {
found = true;
document.getElementById("startbutton").disabled = false;
document.getElementById("guess").value = '';
alert("Game over!");
}

}

var guessingGame = new gameObject();

The interesting thing here is that the only things we're exposing to the outside world in this object are the three privileged methods: start, play and cheat. As in the previous incarnation of this game, the cheat() method gives the answer but ends the game there and then. You don't get the satisfaction of being congratulated for entering the right number if you had to ask for it! Using the earlier solution for this game, it would have been possible to access any guessesLeft property and fiddle with it before submitting a guess, thus having an infinite amount of guesses. With this approach, the number of guesses remaining is available only to the instance of the class because it is all held in private properties.

So, to recap:

Private properties:

Declaration:

var myClass = function() {
var privateProperty;
};

Can be accessed by: private methods and privileged methods.
Are not visible outside the object's scope.

Public properties:

Declaration:

var myClass = function() {
this.publicProperty = 'whatever';
};

Can be accessed by: public methods, private methods(*), privileged methods.
Are visible outside the object's scope.

Private methods:

Declaration:

var myClass = function() {
function privateMethod() {
/* stuff goes here */
}
};

Can access: public methods(*), other private methods, privileged methods(*), private properties, public properties(*).
Can be accessed by: other private methods, privileged methods.
Are not visible outside the object's scope.

Public methods:

Declaration:

yourObject.publicMethod = function() {
/* stuff goes here */
};

- or -

yourClass.prototype.publicMethod = function() {
/* stuff goes here */
};

Can access: other public methods, privileged methods, public properties.
Can be accessed by: other public methods, private methods(*), privileged methods.
Are visible outside the object's scope.

Privileged methods:

Declaration:

var myClass = function() {
this.privilegedMethod = function() {
/* stuff goes here */
};
};

Can access: private methods, public methods, other privileged methods, private properties, public properties.
Can be accessed by: public methods, private methods(*), other privileged methods.
Are visible outside the object's scope.

The more astute readers will have noticed that an asterisk appears sometimes when it comes to private methods' access to public properties and methods and privileged methods. This is down to an error in the implementation of ECMA-262 where this incorrectly references the window object in private methods called by another method of an object when it should reference the object in which the method is running. Consider this scenario:

var myClass = function() {

this.publProp = 'I am a public property of myClass';

this.privilegedMeth = function() { privMeth(); };

function privMeth() { alert(this.publProp); }

}

var myObj = new myClass();
myObj.privilegedMeth();

This should work. Upon invoking myObj.privilegedMeth(), the private method privMeth() is invoked. This, in turn should be able to access the public property publProp and display it in an alert box. Well, it doesn't, because once execution enters the private method, this no longer references your instance of the myClass class, myObj. Instead, it references window. Given that global variables and functions are actually properties and methods of window, you can see this in action by creating a global variable called publProp and giving it a content different from the public property of your object:

var publProp = 'variable in the global namespace';

var myClass = function() {

this.publProp = 'I am a public property of myClass';

this.privilegedMeth = function() { privMeth(); };

function privMeth() { alert(this.publProp); }

}

var myObj = new myClass();
myObj.privilegedMeth();

An alert box saying "variable in the global namespace" will be displayed, which is definitely not the expected behaviour!

We can get around this problem with a bit of a kludge. We create a private property (usually called that, but "that" is a matter of personal taste...) and assign the value of this to it when the object is instantiated. Thereafter, we prefix the public and privileged data we want to access from within the private method with "that." instead of "this." and everything works again because that hasn't changed since the object was instantiated (whereas this now references window). The code snippet above becomes:

var myClass = function() {

var that = this;
this.publProp = 'I am a public property of myClass';

this.privilegedMeth = function() { privMeth(); };

function privMeth() { alert(that.publProp); }

}

var myObj = new myClass();
myObj.privilegedMeth();

You now get the expected result.

So, the point of this article was to explain how to tidy up the potential mess that can arise when multiple scripts are included in a web page. It explored two ways to achieve that and outlined the main differences between them. In doing so it highlighted a pitfall that's the result of a flaw in most browsers' implementation of ECMA-262 and provided a way to bypass that flaw.

Obviously, much more complex stuff can be done than was explored here. For starters, none of the functions and methods given as examples actually take any parameters, while nothing's to stop them doing so. The constructor can also take parameters. For example, in the guessing game object, we can set the maximum allowable number of guesses by saying how many we want to allow the player in the constructor:

var gameObject = function(maxG) {

var guessesLeft;
var found;
var numToGuess;
var yourGuess;
var initialised = false;
var maxGuesses = maxG;

this.start = function() {
guessesLeft = maxGuesses;
found = false;
numToGuess = 1 + Math.floor( Math.random() * 10 );
document.getElementById("startbutton").disabled = true;
initialised = true;
}
...
...
}

var guessingGame = new gameObject(3);

This time, when the class is instantiated, the parameter passed to the constructor, the value 3, is assigned to the private property maxGuesses. When the privileged method start() is called, we no longer blindly assign the value 5 to the private property guessesLeft, we assign maxGuesses to it. We have, in effect, created a variant of the game in which only 3 (the value passed to the constructor) guesses are permitted.

References to methods can also be passed to functions and used as callbacks. This is used frequently in the AJAX subsystems of Prototype and jQuery, where you pass a reference of a function to be called when the AJAX call has completed. You can pass a reference to a private, public or privileged method, although you might have to use the old "that" trick if you want to pass a reference to a public or privileged method when setting up the AJAX call from within a private method. You can also use Prototype's bind method to ensure that your methods run in the desired scope/context.

No comments: