Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Instantiate one instance of a module pattern conditionally in javascript

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?

like image 981
Travis J Avatar asked Dec 01 '25 02:12

Travis J


2 Answers

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.

like image 195
Julien Royer Avatar answered Dec 02 '25 15:12

Julien Royer


What I had intended was to instantiate obj.foobar = new foobar() when obj.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
like image 39
Bergi Avatar answered Dec 02 '25 17:12

Bergi