Monday, May 12, 2008

JavaScript namespace function

As a follow-up to my "Respecting the JavaScript global namespace" post, here's a function that can be used to quickly and easily create namespaces:

(Updated to use a global function instead of extending the String prototype.)

var namespace = function(name, separator, container){
  var ns = name.split(separator || '.'),
    o = container || window,
    i,
    len;
  for(i = 0, len = ns.length; i < len; i++){
    o = o[ns[i]] = o[ns[i]] || {};
  }
  return o;
};

Here's the same code, compressed using YUI Compressor:

var namespace=function(c,f,b){var e=c.split(f||"."),g=b||window,d,a;for(d=0,a=e.length;d<a;d++){g=g[e[d]]=g[e[d]]||{}}return g};

Ideally, this function would be assigned to some other object (to respect the global namespace), unless accepted as a standard function as mentioned above.

See also:

Unlike the "Namespacing made easy" post, this solution doesn't require Prototype. This solution also prevents overwriting existing objects and returns the created "namespace".

Example of use:

namespace("com.example.namespace");
com.example.namespace.test = function(){
  alert("In namespaced function.");
};

Or, as one statement:

namespace("com.example.namespace").test = function(){
  alert("In namespaced function.");
};

Either is then executed as:

com.example.namespace.test();

Update (2008-05-13):

The concern was raised that the "len" variable in my for-loop is being created in the global scope / "namespace", which would contradict the entire purpose of this post! :-)

Fortunately, according to developer.mozilla.org, all variables declared after "var" are created within the same scope - and if declared within a function, the scope is that current function.

Here's my "test case". Use Firebug. First check that neither the "i" nor "len" variables already exist. Then run the following code, and note that neither variable is created in the global scope.

(function(){
  for(var i=0, len=0;false;){};
})();

Compared to this version, which does populate "len" into the global scope:

(function(){
  len = 0;
  for(var i=0;false;){};
})();

Maybe surprisingly, this makes "len" local again, even though it is assigned to before being declared with "var". This is because JavaScript does not have block scope.

(function(){
  len = 0;
  for(var i=0, len=0;false;){};
})();

This general practice is documented at http://www.prototypejs.org/api/array, where it is also explained how caching the length property is an aid to performance. (Though for the stated purpose of creating namespaces, which will probably only be handling single-digit lengths for the majority of the cases, the savings will be practically zero.)

19 comments:

Anonymous said...

By the way - the 'len' variable is on the global scope ;-)

Mark A. Ziesemer said...

remy - please correct me if you still think I'm incorrect, but the documentation I'm reading and the examples I've run show that the "len" variable is in the same (local) scope as "i".

I'm adding to the original post to make use of formatting that isn't supported within comments here.

Anonymous said...

shoudnt we check weather the namespace is already created or not? nice code though!
I found another nice example here: http://www.dottostring.com/2008/11/how-to-namespace-your-javascript/

Mark A. Ziesemer said...

It may not be exactly clear, but it is already being checked for.

Look at the part within the for loop. If the object exists, it is used, otherwise a new object ({}) is used.

I just looked at your link, and it does this part exactly the same way. However, it is also hard-coded to check against a "DTS" parent, which I'm not at all a big fan of - as noted in the link to the YUI bug #1944624.

Anonymous said...

Mark,

It's really cool of you to post this namespacing technique. However, as an intermediate Java programmer who is pretty new to JavaScript, I'm pretty confused about how to make use of the namespace function to ensure that my classes all stay within my defined namespace. Is there any way you could post a zip file or something of a mini-quickstart app that just shows 3 js files that do simple stuff and take advantage of your namespacing technique?

That would be a very much appreciated help.

Thanks,
Jamie

Johan Sundström said...

Mark, in javascript, any variable that is not explicitly declared with the "var" keyword will become global, wherever you defined it. This is one of javascript's most unfortunate big warts. Try this sequence of inputs in the Firebug console if you want to convince yourself -- the latter "len" will be 12 in the global environment:

len; i; function oops() { len = 1; var i = 0; } oops(); len; i;

Johan Sundström said...

Oh, dang; pardon my restating the update section above.

Mark A. Ziesemer said...

jpswain09 - Sorry for the late response, but an example is now included.

Anonymous said...

So do I have to implement the function in every javascript file?

It appears that if the namespace function is declared as namespace = function that it becomes a global so I can then use it from all of my javascript files.

The problem is that it then pollutes the global name space. On the other hand if I implement it as var namespace = function then I have to have it in every script file.

Have I understood this correctly?

Also can you add an example of how to call a namespaced function from xul.

Mark A. Ziesemer said...

Anonymous: Not necessarily implemented once per file, but once per scope. On the web and in XUL, this is pretty much once per window. I would always declare it using "var". Whether it becomes global or not depends upon the scope in which it is defined. For now, I would declare this within a closure / own function block, and then create the namespaces while you still have a valid reference to the defined namespace function.

Unfortunately, I don't see a better solution for this, short of such a namespace function or other equivalent functionality being accepted as a language standard.

Calling a namespaced function from XUL isn't any different than the example given above in the post.

Daishi Kaszer said...

Hi!

This namespace function is a great idea. But is there a way to add private variables and functions ?

Dustin Diaz does it with his namespace code : http://www.dustindiaz.com./namespace-your-javascript/

Anyway, keep up the good work!

Antonio said...

Hi there,

Running the following code gives me a warning in Firefox 3.6.13:

==== code ====
var namespace = function(name, separator, container){
var ns = name.split(separator || '.'),
o = container || window,
i,
len;
for(i = 0, len = ns.length; i < len; i++){
o = o[ns[i]] = o[ns[i]] || {};
}
return o;
};
==== code ===

"Warning: reference to undefined property o[ns[i]]"

I would like to minimize the number of warnings I produce. Any idea on how to avoid it? Thanks in advance

Mark A. Ziesemer said...

Antonio - I completely support your effort, as I insist that all my code is warning-free as well. That said, I'm not able to reproduce the issue that you're observing. Are you seeing it in the initial evaluation of the function, or in an execution of the function? If an execution, can you please provide an example call that demonstrates the warning you observed? Thanks!

binarez said...

Hi, I added a recursive twist to your approach:

var _ = function(name, container) {
  var ns = name.split('.');
  var o = container || window;
  var i = 0;
  var len = ns.length;
  for(; i < len; ++i) {
    o = o[ns[i]] = o[ns[i]] || {};
  }
  o._ = function(name){ return window._(name, o);}
  return o;
};

If I declare:
_('module1')._('sub1');

I can do:
module1.sub1.testFunc = /* ... */;

But I can also use the namespace at the moment of declaration:
_('module1')._('sub1').testFunc = /* ... */;

I kept the dot separator approach, so I could also just do:
_('module1.sub1');

Anonymous said...

IE7 seems to break on this line:
o = o[ns[i]] = o[ns[i]] || {};

Mark A. Ziesemer said...

Anonymous - I am using this code extensively with IE7, and haven't observed any issues. You will need to provide additional details - such as any errors received, expected vs. observed results, etc.

johnbk said...

That is so simple and elegant, I am using it in my project...Thanks!

Michal Zimmermann said...

Hi,
could you explain how exactly this piece of code is supposed to work?

o = o[ns[i]] = o[ns[i]] || {};

Mark A. Ziesemer said...

Michal - another way to write this would have been:

if(o[ns[i]]){
o = o[ns[i]];
}else{
o[ns[i]] = {};
o = o[ns[i]];
}

As is / in short-hand form:

o = o[ns[i]] = o[ns[i]] || {};

The first portion:

o = o[ns[i]] =

Assigns the end result simultaneously to both o and o[ns[i]].

The remaining portion uses o[ns[i]] as the value for the assignment if it evaluates to true (non-null, etc.) - or a new object ({}) if the child doesn't already exists.