Bosworth99
Bosworth99

Reputation: 4234

Javascript module pattern - losing scope of this

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?

Upvotes: 2

Views: 448

Answers (2)

RobG
RobG

Reputation: 147373

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?

Upvotes: 2

Brandan
Brandan

Reputation: 14983

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.

Upvotes: 1

Related Questions