Sunday, February 8, 2009

Namespaces in javascript 1

Here are some of the issues I want to cover - not just in this post but maybe in several including this one:
  • What is the best way to structure a javascript project?
  • How do you package it and handle versioning?
  • How do you prevent clashes in the "global namespace"?
  • In short: how do you create an eco-system of javascript libraries created by multiple authors/vendors with multiple versions which other authors can easily use and handle in a reliable, controlled way?

The Namespace / Global Problem

It's easy to create small useful components or helper libraries in javascript which you could publish, say to github.com, and have other people use. But, if enough of this happens and you don't pay attention to how the global namespace[1] is used then you will eventually get some namespace clashes. For my part, I'm interested in how to safely namespace javascript components written by me or other people.

Creating a simple namespace

First off, easiest way to create a global namespace (ie a global variable that acts as a namespace) is to do something like:
var NS1={};
between the script tags on your web page or in a .js file referenced by your webpage.Then, you can proceed to use NS1 to namespace any other object constructors, functions, variables you care to use.
NS1.Object1 = function() {...}
var o = new NS1.Object1();
A popular javascript library like prototype already stakes its claim to globalisation by taking some of the more obvious global variable names eg 'Ajax', 'Event' etc JQuery is more cautious and puts everything in a 'jQuery' namespace. Both libraries use '$' as a convenience global variable.In:
Douglas Crockford and Yahoo! discuss suggested practices. If you're a company called Yahoo, then you use a global variable YAHOO (which I'll refer to as a global namespace[1]). Douglas suggests using all-caps to make it more obvious that this global variable has a special purpose - to be a global namespace.The module pattern in the second link takes it one step further and suggests a way to build up a group of both public and private variables and functions (a "module") which are built within the scope of an anonymous function. The object returned by this anonymous function is called a "module" and is referenced by a name within the global namespace - in this case "module1":
YAHOO.module1 = function() {
  var private_var1 = .... ;
  ...
  return {
    Object1: function() { ... }
  };
  ...
}();
The returned object or module will have public variables and functions - in the example above, Object1 is a public constructor function of the returned anonymous object. This extends the YAHOO.module1 namespace to YAHOO.module1.Object1.
Which we can use like this:
var o = new YAHOO.module1.Object1();
It is just as easy to have private functions, objects and variables simply by using the 'var' statement within the anonymous function.

Shortcuts for namespaces

It can get tiresome having to reference an object or field by its fully qualified namespaced name.We can create a shortcut like this:
var O = YAHOO.module1.Object1
and then use it
var o = new O()
This is great except where "O" happens to be global.You might be tempted to set O globally, then just casually reference it inside your application:
var O = YAHOO.module1.Object1;
var NS1.SomeObject2 = function() {
  this.doSomething = function() {
  ...
  var o = new O();
  ...
  }
}
In the above, NS1 is a global namespace for some publisher of javascript widgets.The problem is that NS1 has used "O" as a global variable shortcut for a library that it is using from the YAHOO global namespace (it could just as easily be some other object in the same namespace).What happens if NS2 comes along and builds a javascript widget that relies on NS1's "SomeObject2"?
Nothing much except if NS2 in turn decides to use "O" as its global shortcut for some other object that it is using.Bear in mind, the application that NS2 is building will load on the page first by loading NS1 which in turn will first load the YAHOO module1 and its object.So the load order is YAHOO, NS1 and NS2. The global "O" will get set first by NS1 and then overwritten by NS2.
Then, when NS2 runs and instantiates the object in NS1, "O" no longer means what NS1 intended. This of course illustrates the problem with using global variables in general.The moral of the story is to set your shortcuts within the scope of your object.
More precisely, it's ok to use a shortcut for your namespace if it gets used then and there; if it gets referenced in a yet-to-be-executed scope (eg some function that gets called later) then we open ourselves up to potential, nasty, hard-to-debug problems especially where multiple vendors/authors are being used.
The module pattern above encourages non-global shortcuts. As with everything else in our module, the anonymous function houses both the stuff of the module and the shortcuts it uses.

[1] - the term "global namespace" can get used with 2 different meanings in this document; (1) the actual global "space" in which global variable names exist; and (2) a given global variable acting as a namespace for given author/vendor.

No comments: