JavaScript has gotten a bad reputation in the software industry. Most of it is related to the browser DOM and incompatibility across browsers. This problem is starting to go away due to great frameworks like Jquery and Prototype. Web 2.0 sites with heavy AJAX functionality forces browser manufacturers to improve their JavaScript interpreters. JavaScript is even becoming a contender on the server side.
Designing objects
If you google ”object oriented design (OOD) using JavaScript” you will find that most people build JavaScript objects using prototype and the “new” keyword. Here is how you normally would create an object:
var Address = function (street) {
this._street = street;
};
Address.prototype = {
setStreet: function (street) {
this._street = street
},
getStreet: function(){
return this._street;
}
}
I create the function Address. This function will work as my constructor. Then I extend Address’ prototype with two functions, setStreet and getStreet. I can now use the “new” keyword to create an address object with the two functions “setStreet” and “getStreet” baked in
var addr = new Address("Elm Street");
If I want to, I can change the street name in the address object
addr.setStreet("Old Berry Vale");
This is all good. However, there are some problems with this approach.
This and that
If you come from OO languages like C# or Java, you are tempted to believe that the “this” keyword refers to the current instance of the Address class. However that’s not always true. The “this” keyword actually refers to the context in which the function is invoked. If somebody for some reason invokes the setStreet function in a different contex you will get into trouble. This usually happens when we pass around function pointers. I can use the "call" function on the "setStreet" function to illustrate this by calling the function "setStreet" in the context of an empty object ({})
addr.setStreet("Old Berry Vale");
areEqual("Old Berry Vale", addr.getStreet()); //nothing strange here
addr.setStreet.call({}, "Cotton Acres"); //calling method from different context
areEqual("Old Berry Vale", addr.getStreet()); //hey.. the name didn't change
Are you protecting your privates ?
Well, are you?
addr._street = "Pleasant Island Stead"; //I can change internal state
areEqual("Pleasant Island Stead", addr.getStreet());
Ai ai ai. The “_street” instance variable isn't really private. Not having private instance variable when you create objects like this is problematic.
Closures. The road to a better approach.
From the description above you might believe that JavaScript is troublesome. However, as with any sharp tool, you need to use it right if you don’t want to get hurt.
JavaScript has the fine notion of closures. And yes, now it’s time to concentrate:
function main(foo) {
runFunction(sayHello);
function sayHello() {
alert(foo);
}
}
function runFunction(callback) {
callback();
}
main("Hello world"); //=> “Hello world”
In JavaScript you can define functions within functions. This might seem a little bit strange, but you get used to it. The ”runFunction” is straight forward. It takes a function pointer as an argument, and executes that function . The “main” function is more fun, but still pretty simple. It just passes the “sayHello” function to the “runFunction” function.
The interesting thing here is that when the “runFunction” invokes the “sayHello” function, the sayHello function knows the value of “foo”. How can this be? The answer is that even after you pass the “sayHello” function it still has access to the parent scope where it was defined. The “sayHello” function is a closure. Cool huh ?
Another thing to mention here is that the “sayHello” function is private within the “main” function.
A better approach
By using closures and a couple of additional tricks you can create “objects” that doesn’t have the problems outlined in the previous solution. This is how you do it:
var Address = function (street) {
function setStreet(s) {
street = s;
}
function getStreet() {
return street;
}
//explicitly make private methods public
return {
getStreet:getStreet,
setStreet:setStreet
};
}
You can now create an “instance” of the “object” this way:
var addr = Address("Elm Street");
Notice we are not using the “new” keyword, and we are not using “prototype”. We are basically calling the Address function which returns an object containing the getStreet and the setStreet function. The “street” variable acts as an instance variable. The function getStreet and setStreet has access to this variable since they are closures.
The setStreet and getStreet functions are initially private so we need to return them in an object to make them public. The act of making functions public is explicit, which I think is a good thing.
We get the expected behaviour:
var addr = Address("Elm Street");
addr.setStreet("Old Berry Vale");
areEqual("Old Berry Vale", addr.getStreet());
addr.setStreet.call({}, "Cotton Acres"); //different context doesn't create problems
areEqual("Cotton Acres", addr.getStreet());
addr.street = "Pleasant Island Stead"; //private variables cannot be tampered with
areEqual("Cotton Acres", addr.getStreet());
Good good. One other thing to notice is that the code is now visually much cleaner. Well, that’s it for now. [UPDATE] Look at the “yahoo javascript module pattern” if you want to learn more [/UPDATE]
A small note on methods and functions
In mathematics a function always returns the same value on the same input. In this post I have used the word in a more loose fashion because the of the “function” keyword in JavaScript. Methods and functions are not the same. Just so you know :)