2

Been working on a js module pattern for a while to meet the escalating needs of "some website". Essentially, I just need a good way of grouping / encapsulating scripts, along with the sometime need for OO patterns.

I've got a structure that works ok, but I'm not happy with certain parts of it... so I'm in the process of refactoring it. Here is the updated pattern:

(function (global) {
    var M = {
        VERSION : 1.0
    };

    global.M = M;

    //jQ document.ready()
    global.onload = function(){
        console.log('M VERSION: %s', M.VERSION);
        var main = new M.Main();
    };

    M.Main = function(){
        var _class1;
        var _class2;
        var _class3;

        function _init(){
            _class1 = new M.Class('foo','baz');
            console.log('_class1.param2 : %s', _class1.getParam() ); //works

            _class2 = new M.OtherClass('faz','boo');
            _class2.setParam('Boozaz');
            console.log('_class2.param2 : %s', _class2.getParam() ); //works

            _class3 = new M.Class('fuz','boz')
            console.log('_class3.param2 : %s', _class3.getParam() ); //works

            _class3.prototype = new M.Super();
            console.log('_class3.__param : %s', _class3.prototype.getProtected() ) //works
        }

        _init();
        return true;
    };

    M.Super = function(){
        var __param = 'jQ';
        M.Super.API = {
            getProtected : function(){ return __param }
        }
        return M.Super.API;
    }

    M.Class = function( p1, p2){
        var _param1;
        var _param2;

        function _init(){
            _param1 = p1;
            _param2 = p2;
        }

        function _getParam(){
            return _param2;
        }

        function _setParam(e){
            _param2 = e;
        }

        M.Class.API = {
            getParam : function(){ return _getParam(); },
            setParam : function(e){ _setParam(e) },
            publicMethod : function(){  ...  }
        }

        publicMethod() //fails
        this.publicMethod() //fails, this scopes to DOM window
        M.Class.API.publicMethod() // works, but is kludgy

        _init();
        return M.Class.API;
    };

})(typeof window === 'undefined' ? this : window);

This produces a satisfactory DOM structure (on inspection via firebug) - but I'm losing scope of this in one specific area = calling the "public" methods of the returned object, internally.

publicMethod() //fails
this.publicMethod() //fails, this scopes to DOM window
M.Class.API.publicMethod() // works, but kludgy syntax

In the previous iteration of this pattern, the "class" object is self-executing, and reference to this is maintained :

    M.Class = function( p1, p2){
        var _param1;
        var _param2;
        var _root; 

        function _init(){
            _root   = this; //this gets referenced for later usage
            _param1 = p1;
            _param2 = p2;
        }

        function _getParam(){
            return _param2;
        }

        function _setParam(e){
            _param2 = e;
        }

        M.Class.API = {
            init     : function(){ _init();    },
            getParam : function(){ return _getParam(); },
            setParam : function(e){ _setParam(e) },
        }

        console.log('getParam, internal :%s', _root.getParam() ) //success
        return M.Class.API;
    }();

  M.Class.init();

However, in the refactored pattern, I wish instantiate these "classes" via new, to gain more control over execution order.

I've read many, many articles on the fairly mind numbing subject of lexical scope in js... yet come up with no conclusions.

How should I maintain the scope of this in my updated module pattern?

TylerH
  • 20,799
  • 66
  • 75
  • 101
Bosworth99
  • 4,206
  • 5
  • 37
  • 52
  • Say 1,000 times: **this is not scope, it's a special local variable set by the call** (or ES5 *bind*). – RobG Feb 17 '12 at 03:06
  • Not sure how `this` is scoping to `window` and not `M.Class`. An obvious solution, however maybe not feasible for you, would be to declare a wrapper function in M.Class to call M.Class.API's equivalent function for you. If you're attempting to set what `this` refers to within `publicFunction` look into `call()` and `apply()`. – dennmat Feb 17 '12 at 03:12
  • How do you use `M.Class` inside of `M.Class`? It should be undefined, isn't it? – Nemoden Feb 17 '12 at 03:14
  • @Nemoden no it should be defined within it. Functions can reference themselves. http://jsfiddle.net/DgBaa/1/ – dennmat Feb 17 '12 at 03:30

2 Answers2

2

This is one of those philosophical questions that everyone asks when writing a library or module: should functions reference the container object using this or the variable name? The answer is: it depends.

If you know that the function will always be called with the correct value of this (e.g. a method on a prototype), then use this. However, if the function might be called any other way, then use the variable name. If you decide at some later stage to change the name, it's a pretty simple search and replace exercise. And calling myLib.utils.someFn is a lot clearer than calling this.someFn to me. And if you feel it's too much typing, you can always revert to var sF = myLib.utils.someFn and go from there.

Edit

To answer your questions:

publicMethod() //fails

There is no publicMethod identifier in the current scope, it will fail.

     this.publicMethod() //fails, this scopes to DOM window

If the call was M.Class(), then this is a reference to M. If you are getting window, then you are calling the function some other way.

     M.Class.API.publicMethod() // works, but is kludgy  

Because that is how you have set it up. If you don't like it, set it up some other way.

Lastly:

)(typeof window === 'undefined' ? this : window);

seems to be one of those mystic incantations that seem to proliferate on the web. What is the purpose? If the intent is to pass a reference to the global object to the function, then:

)(this);

is sufficient everywhere. The purpose of the above is to ensure that the function has a reference to the global object because referencing window might resolve to some other object. Including logic that may or may not pass in the global object seems like a backward step. In what scenario is it preferable to reference the (possibly re-assigned) window property of the global object rather than the global object itself?

RobG
  • 142,382
  • 31
  • 172
  • 209
  • There is a difference if there are multiple "instances" of the `function`. Using the name directly is similar (however not exactly the same) as declaring a static member in OO languages on a class. – dennmat Feb 17 '12 at 04:57
  • Naturally if there are multiple instances, object properties should be set up so *this* references the instance. However, it all goes back to how functions are called—you can only guarantee the value of *this* where you are in control of the call. – RobG Feb 17 '12 at 05:05
  • @RobG - thanks for the reply. Funny and frustrating how much _philosophy_ comes into play when structuring js. Many valid ways to do a single thing... At any rate I totally get what you're saying. I think what probably makes sense is to use both patterns when applicable. Some "classes" in this architecture, after all, only need a single instance. As for your individual points - I was just very surprised that 'this' resolved to 'Window' in the second situation (posted code being only slightly modified from my live code) perhaps I have some wires crossed... – Bosworth99 Feb 17 '12 at 16:43
  • @RobG ... Also - whats not to love about _mystical incantations_ in your code. Everyone who looks at it (your boss, especially) will say to themselves "wow. you are, indeed, a smart fellah". Lol. You're point is well taken. I think I scraped it up from some post long ago, and just seemed to be a good failsafe when setting up an initial object. But you're right - what the hell else would 'this' refer to in that situation? Cheers... – Bosworth99 Feb 17 '12 at 16:46
  • 1
    Ha! Yes, people love those things, they're so damn sexy. Until someone asks "why". :-) – RobG Feb 18 '12 at 02:36
1

What if all of your methods were "private" except the ones you manually expose through M.Class.API?

function _getParam(){
  return _param2;
}

function _setParam(e){
  _param2 = e;
}

function publicMethod(){
  console.log("public method");
}

M.Class.API = {
  getParam : _getParam,
  setParam : _setParam,
  publicMethod : publicMethod
}

publicMethod();              // succeeds
this.publicMethod();         // still fails
M.Class.API.publicMethod();  // still works, still is kludgy

You should also be aware that returning an anonymous object from a function may have unintended consequences when calling that function with the new keyword. See this Stack Overflow question.

Community
  • 1
  • 1
Brandan
  • 14,735
  • 3
  • 56
  • 71
  • Interesting point on your attached link. I seldom do custom type checking in JS just for this reason (rather - a more cumbersome prop check like 'is obj typeof 'object' && obj.hasOwnProperty('someProp') && obj['someProp'] != 'undefined' '. At any rate - "public" methods that just invoke "private" methods can certainly work, and may be included in my final refactor... Cheers! – Bosworth99 Feb 17 '12 at 16:52