B.log

Random notes mostly on Machine Learning

Namespaced Methods in JavaScript

Once upon a time I was asked (well, actually a question wasn't for me only, but for whole habrahabr's community) is it possible to implement namespaced methods in JavaScript for built-in types like:

5..rubish.times(function() { // this function will be called 5 times
  console.log("Hi there!");
});

"some string".hask.map(function(c) { return c.hask.code(); });
// equivalent to
"some string".split('').map(function(c) { return c.charCodeAt(); });

"another string".algo.lcp("annotation"); 
// returns longest common prefix of two strings

As you can see at the link, it's possible using ECMAScript 5 features. And that's how:

First, let's point out the main problem with the straightforward approach: it doesn't work when you write

Class.prototype.ns.method = function() {
  return this.methodA() + this.methodB();
}

this points to the Class.prototype.ns instead of an instance of Class. The only way to change it is rebind this to the our instance like this:

var instance = new Class;
instance.ns.method.call(instance);

Obviously, it's not a solution since in that case it is a lot easier to write something like

var instance = new Class;
MegaLibrary.method(instance);

Thus we need to somehow return a correct function (with this binded to the instance) when user calls namespaced methods. This can be done using getters.

When user accesses our namespace we give him a proxy-object with a custom getter for every method in the namespace. This getter returns a method with rebinded this. The question is: how do we get a reference to the instance? The answer is pretty simple: using getters again! Instead of declaring an ordinary property for the namespace we can create a property with a custom getter memoizing a reference to this. Voilà!

Finally, the code is:

But wait... How cross browser is it?

Well, I'm pretty lazy to test it on all platforms (IE, Opera, FF, Chrome, Node.JS), so I'll do like a mathematician in a famous anecdote:

Three employees (an engineer, a physicist and a mathematician) are staying in a hotel while attending a technical seminar. The engineer wakes up and smells smoke. He goes out into the hallway and sees a fire, so he fills a trashcan from his room with water and douses the fire. He goes back to bed. Later, the physicist wakes up and smells smoke. He opens his door and sees a fire in the hallway. He walks down the hall to a fire hose and after calculating the flame velocity, distance, water pressure, trajectory, etc. extinguishes the fire with the minimum amount of water and energy needed. Later, the mathematician wakes up and smells smoke. She goes to the hall, sees the fire and then the fire hose. She thinks for a moment and then exclaims, 'Ah, a solution exists!' and then goes back to bed.

As you can see, the key part of code is ECMAScript 5's Object.defineProperty. According to the kangax's ECMAScript 5 compatibility table it has pretty good support:

  • IE 9+
  • Opera 12+
  • FF 4+
  • Chrome 7+ (and thus Node.JS too)
comments powered by Disqus