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?

1
  • There - changed my code according to Bergi's and Beetroot-Beetroot's suggestion. Remains to solve this little caveat: stackoverflow.com/questions/17864608/… ... Commented Jul 25, 2013 at 17:18

3 Answers 3

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

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

2 Comments

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...(?)
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!
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
    };
}();

1 Comment

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'.
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

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.