3

I spent the better part of the day reading about the module pattern and its 'this' scope. Eventually I found a work-around for my problem, although with a feeling there's a better way of doing things.

The actual code is >200 lines, but I've boiled it down to the following: objA has a method (publicA) that objB wants invoke by callback. The detail that complicates things is that publicA needs help from publicA_helper to do its job. (http://jsfiddle.net/qwNb6/2/)

var objA = function () {
    var privateA = "found";
    return {
        publicA: function () {
            console.log("privateA is " + this.publicA_helper());
        },
        publicA_helper: function () {
            return privateA;
        }
    };
}();

var objB = function () {
    return {
        callback: function (callback) {
            callback();
        }
    }
}();

objA.publicA(); // privateA is found
objB.callback(objA.publicA); // TypeError: Object [object global]

Fair enough – I've grasped that the caller's context tends to influence the value of 'this'. So I add measures to retain 'this' inside objA, of which none seems to work. I've tried the var objA = (){}.call({}) thingy, setting var self = this; (calling self.publicA_helper() accordingly). No luck.

Eventually, I added a private variable var self;, along with a public method:

init: function() {self = this;},

...and by making sure I call objA.init(); before passing objA.publicA to objB.callback, things actually work.

I cannot stress the immensity of the feeling that there's a better way of doing this. What am I missing?

o-o
  • 8,158
  • 2
  • 20
  • 21
  • There - changed my code according to Bergi's and Beetroot-Beetroot's suggestion. Remains to solve this little caveat: http://stackoverflow.com/questions/17864608/js-revealing-module-pattern-accessing-internal-objects-vs-arrays ... – o-o Jul 25 '13 at 17:18

3 Answers3

3

The generalized solution is extremely simple.

Write all the module's methods as private, then expose those that need to be public.

I write all my modules this way :

var objA = function () {
    var privateA = "found";
    var A = function () {
        console.log("privateA is " + A_helper());
    },
    var A_helper = function () {
        return privateA;
    }
    return {
        publicA: A
        //A_helper need not be exposed
    };
}();

Thus, all methods are in the same scope, each one having direct access to all other methods in the same module, and the ambiguous this prefix is avoided.

objB.callback(objA.publicA); will now work as expected.

See fiddle

Beetroot-Beetroot
  • 18,022
  • 3
  • 37
  • 44
  • Thanks Beetroot - I granted Bergi the answer due to its level of detail. This pattern has a charm in both readability and flexibility - I can easily see and alter what members are exposed. I cannot see why I would ever want to go back to my old way, that of actually defining public methods inside the return clause...(?) – o-o Jul 25 '13 at 14:50
  • OK but the part of Bergi's answer you actually want is the last part, which is the same as mine, posted 2 hours earlier! – Beetroot-Beetroot Jul 25 '13 at 19:45
1

I've tried the var objA = (){}.call({}) thingy,

How? You want to use call on the callback that you want to invoke with a custom this, not on your module closure. It should be

var objB = {
    callback: function (callback, context) {
        callback.call(context);
    }
};

objB.callback(objA.publicA, objA);

I've tried setting var self = this;

The self variable is supposed to be in a closure and point to the object on the methods are stored. That is only this when your module IEFE would be invoked on your module - it's not. Or if it was a constructor - it's not. You could change that with call as above:

var objA = function () {
    var privateA = "found",
        self = this;
    this.publicA = function () {
        console.log("privateA is " + self.publicA_helper());
    };
    this.publicA_helper = function () {
        return privateA;
    };
    return this;
}.call({});

But that's ugly. In your case, the self variable simply needs to point to the object literal which you're returning as your module:

var objA = function () {
    var privateA = "found",
        self;
    return self = {
        publicA: function () {
            console.log("privateA is " + self.publicA_helper());
        },
        publicA_helper: function () {
            return privateA;
        }
    };
}();

Btw, since you're creating a singleton you don't need an explicit self, you could just reference the variable that contains your module (as long as that doesn't change):

var objA = function () {
    var privateA = "found";
    return {
        publicA: function () {
            console.log("privateA is " + objA.publicA_helper());
        },
        publicA_helper: function () {
            return privateA;
        }
    };
}();

Another method would be to simply make all functions private and then expose some of them - by referencing them local-scoped you will have no troubles.

var objA = function () {
    var privateA = "found";
    function publicA() {
        console.log("privateA is " + helper());
    }
    function helper() {
        return privateA;
    }
    return self = {
        publicA: publicA,
        publicA_helper: helper // remove that line if you don't need to expose it
    };
}();
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Thanks Bergi for that thorough answer. That last pattern of yours is hard to argue against, avoiding 'this' altogether (see my comment on Beetroot-Beetroot). Regarding your comment on singleton: that's actually the method I used that originally got me here. I just had a feeling that calling objA.publicA time upon time meant a lot of traversing the scope-chain (as objA isn't found until reaching global context). I also wanted a pattern that is general, i.e. not 'singletons-only'. – o-o Jul 25 '13 at 14:59
0

The reason is that the context is getting changed when you are invoking the callback. Not a generalized solution, but shows that the code works by specifying the context while invoking callback.

var objA = function () {
  var privateA = "found";
  return {
    publicA: function () {
        console.log("privateA is " + this.publicA_helper());
    },
    publicA_helper: function () {
        return privateA;
    }
  };
}();

var objB = function () {
    return {
        callback: function (callback) {
          callback.call(objA);
        }
   }
}();

objA.publicA(); // privateA is found
objB.callback(objA.publicA); // privateA is found
sreekarun
  • 167
  • 3