3

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?

2 Answers 2

4

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.

Sign up to request clarification or add additional context in comments.

Comments

2

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.