Reputation: 82267
I am trying to expose a module. I wanted to expose only one instance of it to all callers, and I want to wait until the module is called to instantiate it. I tried to do this:
var obj = {};
var foobar = function(){
var id=22;
function GetId(){ return ++id; }
return{ GetId: GetId };
};
obj.foobar = (function(){
if (obj.foobar instanceof foobar) {
return obj.foobar;
}
return new foobar();
})();
console.log(obj.foobar.GetId());//23
console.log(obj.foobar.GetId());//24
But really it is just an obfuscation of
obj.foobar = new foobar();
What I had intended was to instantiate obj.foobar = new foobar()
when obj.foobar.GetId()
is called the first time, and the second time obj.foobar.GetId()
is called use the already instantiated version. Although not present here, there are dependencies which require waiting to instantiate new foobar();
so it cannot be executed right away.
How can I accomplish this, what did I miss?
Upvotes: 3
Views: 175
Reputation: 1429
You can use a function call each time you access foobar
:
obj.foobar = (function() {
var inst;
return function() {
return inst || (inst = foobar());
};
})();
console.log(obj.foobar().GetId()); // 23
You can also use ECMAScript 5's named accessor properties if the targeted execution environments support them:
Object.defineProperty(obj, "foobar", {
get: (function() {
var inst;
return function() {
return inst || (inst = foobar());
};
})()
});
console.log(obj.foobar.GetId()); // 23
Alternatively, provided that you know the list of methods which can be called on foobar
, you can use a more complex solution:
obj.foobar = (function() {
var inst, res = {}, methods = ["GetId"];
function createLazyMethod(method) {
return function() {
if (!inst) {
obj.foobar = inst = foobar();
}
return inst[method].apply(inst, methods.slice.call(arguments, 0));
};
}
for (var i = 0; i < methods.length; ++i) {
res[methods[i]] = createLazyMethod(methods[i]);
}
return res;
})();
console.log(obj.foobar.GetId()); // 23
With this solution, once foobar
has been instantiated, calls to its methods come at zero cost.
Upvotes: 4
Reputation: 664405
What I had intended was to instantiate
obj.foobar = new foobar()
whenobj.foobar.GetId()
is called the first time
No, that doesn't work. If you call the getId
method, there must already be an existing object. Either you define a getter function for the foobar
property of the obj
which creates the instance on accessing, or you just instantiate it before (as you did in your IEFE and could have shorter done with the assignment, as you said).
Usually, you would use a function (which could also be a constructor) that you call each time and that returns the singleton if it was already created, else it creates one and stores it:
var obj = {
foobar: (function iefe() {
var id, instance;
return function constructor() {
if (!instance) { // create it
id = 22;
instance = {
getId: function getID(){
return ++id;
}
};
}
return instance;
};
})();
};
obj.foobar().getId() // 23
obj.foobar().getId() // 24
Upvotes: 2