javascriptscopepublic-members

How can I scope this "public" method correctly?


I have this code (JSFiddle)

var OBJ = function(){
    var privateVar = 23;
    var self = this;

    return {
        thePrivateVar : function() {
          return privateVar;
        },  

        thePrivateVarTimeout : function() {
            setTimeout(function() { alert(self.thePrivateVar()); } , 10);
        }
    }

}();

alert(OBJ.thePrivateVar());

OBJ.thePrivateVarTimeout();

This is an abstraction of a real problem I'm having.

So - I would expect the call to OBJ.thePrivateVarTimeout() to wait 10 and then alert with 23 (which I want it to access through the other exposed method).

However self doesn't seem to be setting correctly. When I am setting self = this it appears that this isn't a reference to the function but a reference to the global object. Why is this?

How can I make the public method thePrivateVarTimeout call the other public method thePrivateVar?


Solution

  • var OBJ = (function(){
        var privateVar = 23;
        var self = {
            thePrivateVar : function() {
              return privateVar;
            },  
    
            thePrivateVarTimeout : function() {
                setTimeout(function() { alert(self.thePrivateVar); } , 10);
            }
        };
    
        return self;
    
    }());
    

    this === global || undefined inside an invoked function. In ES5 it's whatever the global environment is, in ES5 strict it is undefined.

    More common patterns would involve using the var that = this as a local value in a function

    var obj = (function() {
      var obj = {
        property: "foobar",
        timeout: function _timeout() {
          var that = this;
          setTimeout(alertData, 10);
    
          function alertData() {
            alert(that.property);
          }
        }
      }
    
      return obj;
    }());
    

    or using a .bindAll method

    var obj = (function() {
      var obj = {
        alertData: function _alertData() {
          alert(this.property);
        }
        property: "foobar",
        timeout: function _timeout() {
          setTimeout(this.alertData, 10);
        }
      }
    
      bindAll(obj)
    
      return obj;
    }());
    
    
    /*
        bindAll binds all methods to have their context set to the object
    
        @param Object obj - the object to bind methods on
        @param Array methods - optional whitelist of methods to bind
    
        @return Object - the bound object
    */
    function bindAll(obj, whitelist) {
        var keys = Object.keys(obj).filter(stripNonMethods);
    
        (whitelist || keys).forEach(bindMethod);
    
        function stripNonMethods(name) {
            return typeof obj[name] === "function";
        }
    
        function bindMethod(name) {
            obj[name] = obj[name].bind(obj);
        }
    
        return obj;
    }