Namespacing made easy
Enumerable#inject is one scary method. It took me a while to understand the beauty of this functional programming paradigm. Just in case you were ever wondering about its real life usage – here’s one of them.
We all know that using namespaces is generally a wise thing to do. You don’t have to create a multi level object structure, but nesting your code under a pseudo-namespace is what makes maintenance, upgrades and migrations somewhat easier and error-prone.
Few popular javascript libraries implement some sort of a utility function that automatically creates nested objects. What it means is that instead of writing it manually, you could let the helper handle it for you:
// creating nested structure manually is painful
var com = {
thinkweb2: {
projects: {
_prototype: 'a deeply nested property...'
}
}
}
com.thinkweb2.projects._prototype // => 'a deeply nested property...'
Well, it appears that inject can handle such task in only 3 lines of code:
'com.thinkweb2.projects.prototype'.split('.').inject(window, function(parent, child) {
var o = parent[child] = { }; return o;
})
Object.inspect(com.thinkweb2.projects.prototype) // => "[object Object]"
Not bad – a completely generic namespacing snippet.
What’s happening here is not a black magic. Inject accepts initial object as a first argument (in this case – window object) and iterator function as the second argument. It then calls iterator function on each item from the collection (in our case it’s simply a string split into an array). Iterator function receives accumulated result as a first argument and current value as a second. Iterator performs an action (creates a nested object) and returns accumulated result (the newly created parent object) to be used in following iterations.
Alternatively, we could wrap this all nicely and define as a String.prototype method:
String.prototype.namespace = function(separator){
this.split(separator || '.').inject(window, function(parent, child) {
var o = parent[child] = { }; return o;
})
}
foo.bar.baz.quux = 'Nothing special...'; // => ERROR: foo is not defined
// Default separator is '.'
'foo.bar.baz.quux'.namespace();
foo.bar.baz.quux = 'Nothing special...'; // => "Nothing special..."
// Or using a custom one
'MY_AWESOME_APPLICATION::util::DOM::dimensions'.namespace('::');
'dimensions' in MY_AWESOME_APPLICATION.util.DOM; // => true
Note how we use “separator” argument (or defaulting to ‘.’) making the function somewhat more flexible.
P.S.
Heavy namespacing could lead to unnecessary complexity.
Lack of it – to buggy behavior.
In the end, the only thing that matters is what works best for you.
Enjoy!
kangax (article author) said on Jan 30, 2008 @ 19:55
#1As Nicolás Sanguinetti pointed out, we could remove the temporary o variable and return object explicitly. The function becomes ultra concise:
String.prototype.namespace = function(separator) {
this.split(separator || '.').inject(window, function(parent, child) {
return parent[child] = { };
})
}
insane said on Feb 1, 2008 @ 11:24
#2wow! I was thinking on js namespaces just a couple of days ago Thnks.
Tim said on Feb 1, 2008 @ 12:22
#3I’ll take the explicit namespacing over this approach because the readability of the former is significantly better than the latter. Most sites/developers can keep their namespace fairly shallow as well which reduces the typing.
Michael Lee said on Feb 1, 2008 @ 12:34
#4Interesting read; novel way of implementing javascript namespaces ;-)
I can see why you chose to extend the String object, but I’m still wary of modifying core object prototypes…That said, the simplicity of your solution is definitely appealing and insightful.
Not sure how serious/interested you are in the JavaScript namespacing topic, but I’d be interested to hear your thoughts on Ajile -: http://ajile.iskitz.com/. It’s my stand-a-lone solution to JavaScript namespace handling and its related concepts like importing and handling ambiguity.
henrah said on Feb 4, 2008 @ 6:19
#6you could replace
return parent[child] = { };with
return parent[child] = parent[child] || { };To avoid overwriting existing objects. For example, if
window.foois already defined, calling'foo.bar.baz'.namespace()will add the.bar.baznamespace without destroying any other properties onfoo.buggedcom said on Mar 8, 2008 @ 5:49
#8checking if a namespace exists
String.prototype.namespaceExists = function(){
var parts = this.split('.'), exists = true, i=0, base=window, l=parts.length;
while(i
buggedcom said on Mar 8, 2008 @ 5:49
#9hmmm… didn’t like that…
String.prototype.namespaceExists = function()
{
var parts = this.split(‘.’), exists = true, i=0, base=window, l=parts.length;
while(i
buggedcom said on Mar 8, 2008 @ 5:50
#10didn’t like that either…. oh well
Nikso said on Apr 18, 2008 @ 16:56
#11Here is my version:
String.prototype.namespace = function(source) {
Object.extend(this.split('.').inject(window, function(parent, child) {
return (parent[child] = parent[child] || { });
}), source || { });
}
That allow:
"Ns.Test".namespace({
Nested: {
Str: "ya!"
},
Cls: Class.create({
name: "Nested class ",
say: function(what) {
alert(this.name + what);
}
})
});
Can be called like:
(new Ns.Test.Cls).say(Ns.Test.Nested.Str);
bb ^_^
Mark A. Ziesemer said on May 12, 2008 @ 8:54
#12Addresses many of the comments made above and elsewhere, and doesn’t require inject:
jQuery How to said on Jan 29, 2009 @ 9:37
#13Great article. Here is an article on how to use it with the jQuery object. In other words namespasing with the jQuery http://jquery-howto.blogspot.com/2009/01/namespace-your-javascript-function-and.html