Saturday, February 25, 2012

A curious module pattern (in javascript)

So one thing that mildly bugs me about Javascript is that creating a project often requires a pattern like this:

;(function(root) {
  var Project = {}
  root.Project = Project
  
  Project.someMethod = ...
  Project.anotherMethod = ...
})(this)

What bugs me in the code above is that I have to come up with a name for the project, and then this name will eventually end up sprinkled all over the codebase. But the thing is: at heart, I'm a coder, not a marketing person. So coming up with a catchy name for my project is not something I generally want to do before I start typing away. And going back and renaming dozens of variable names after the fact is kinda kludgy.

So I was playing around to see if there's a neater way to write my module patterns. Lo and behold, here's what came out:

with ({project: "MyProject", root: this}) new function() {
  var shadow = root[project], self = root[project] = this;
  self.noConflict = function() {
    root[project] = shadow;
    return self;
  }

  self.someMethod = ...
  self.anotherMethod = ...
};

Basically this allows me to declare the name of the library only once without polluting the global namespace, while supporting a jQuery/underscore.js-like noConflict() method.

Now, if anyone is getting ego massage urges at the sight of that with statement at the beginning: yes, I'm perfectly aware it's been called evil by just about every js expert, and quite frankly, I don't think it matters in this case:

  • There's no huge performance hit - it's a module pattern, not an expression deep inside nested for loops.
  • There's no searching for unknown variable names up the scope tree - the only reason you'd want to do that in a module pattern would be to access the global object, and this object is conveniently accessible from the root variable. As it turns out, accessing any other magically global variable would actually be a code smell: you can't expect document to exist in, say, Node.js for example, so assuming that is a global variable breaks portability.
  • There's no unexpected variable shadowing. I know exactly what and where the variables are - they're project and root and they're used exactly 3 times each on the first 3 lines of the constructor function. It's fairly easy to tell I'm not accidentally clobbering hypothetical global variables project and root, if there were such things to begin with.

Generally, it seems like a pretty neat idiom, and it cleanly delimits the boundaries of the project. I don't need to go scrolling down to double check what the exact capitalization of the project namespace is, or whether it's imported into window, this, module.exports, or what. Also, it reminds me a bit of the local keyword in Standard ML. Anyways. Enough babbling for today.

No comments:

Post a Comment

Post a Comment