Thursday 14 March 2013

Singletonize Revisited 2.0

I already posted about "singletonizing" a constructor JavaScript function a while ago. The other day, while going through this beautiful article about the internals of Function.bind, I realized of some limitations of my previous approach. The native implementation of Function.bind is internally more powerful than its JavaScript implementations (prototype.js, es5 shim), as it creates a special function Object with some missing, new and overriden internal properties. One point to note is that contrary to what happens with the JavaScript implementations (which are returning anonymous functions) the name property of a bound function will be the same as that of the original function.

function saySomething(){
    console.log(this.name + " says something");
}

var boundSaySomething = saySomething.bind({name: "xuan"});
console.log(boundSaySomething());
//prints: "xuan says something"

console.log(saySomething.name);
//prints "saySomething"

console.log(boundSaySomething.name);
//prints "saySomething"

In my previous singletonizer, I was also creating an anonymous function, so its name was an empty string. The name property is not writable:

var f1 = function(){
    console.log("hi");
};

console.log(f1.name);
//prints: ""

var nameDescript = Object.getOwnPropertyDescriptor(f1, "name");
Object.keys(nameDescript).forEach(function(key){
    console.log(key + ": " + nameDescript[key]);
});

/* prints:
configurable: false
enumerable: false
value: 
writable: false
*/

so we have no way to to set it once the function has been created. Then, the question is, how do we create a named function with a name that is only known at runtime? One option would be using new Function, but functions created that way do not trap their lexical scope (taken from MDN):

Note: Functions created with the Function constructor do not create closures to their creation contexts; they always run in the window context (unless the function body starts with a "use strict"; statement, in which case the context is undefined).

Well, the only option I can think of is using eval. OK, sure you've heard that eval is evil. Let's clarify this, eval is evil when you are evaluating a third party string (either typed by the user, or returned by some server code not controlled by you), but there's nothing wrong with using eval (performance aside) with a string that is under your control (think of eval as a form of the "compiler as a service" that mono and Roslyn implement). So, we end up replacing this:

var singletonized = function(){ ...

with this:

eval ("var singletonizedFunc = function " + initialFunc.name + "(){" ...

I've also improved a bit my old approach by adding a desingletonize method and also taking into account the case when the constructor function returns a value

You can find the code here.

1 comment: