Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JavaScript 'class' and singleton problems

I have a singleton object that use another object (not singleton), to require some info to server:

var singleton = (function(){

  /*_private properties*/
  var myRequestManager = new RequestManager(params,
    //callbacks
    function(){
        previewRender(response);
    },
    function(){
        previewError();
    }
  );

  /*_public methods*/
  return{

    /*make a request*/
    previewRequest: function(request){
       myRequestManager.require(request);  //err:myRequestManager.require is not a func
    },

    previewRender: function(response){
      //do something
    },

    previewError: function(){
      //manage error
    }
  };
}());

This is the 'class' that make the request to the server

function RequestManager(params, success, error){
  //create an ajax manager
  this.param = params;
  this._success = success;  //callbacks
  this._error = error;
}

RequestManager.prototype = {

  require: function(text){
    //make an ajax request
  },
  otherFunc: function(){
     //do other things
  }

}

The problem is that i can't call myRequestManager.require from inside singleton object. Firebug consolle says: "myRequestManager.require is not a function", but i don't understand where the problem is. Is there a better solution for implement this situation?

like image 648
Manuel Bitto Avatar asked Apr 19 '10 12:04

Manuel Bitto


People also ask

What are singleton classes in Javascript?

Singletons are used to create an instance of a class if it does not exist or else return the reference of the existing one. This means that singletons are created exactly once during the runtime of the application in the global scope. Based on this definition, singletons seem very similar to global variables.

Why should we not use Singleton pattern?

The most important drawback of the singleton pattern is sacrificing transparency for convenience. Consider the earlier example. Over time, you lose track of the objects that access the user object and, more importantly, the objects that modify its properties.

What are the two problems that the Singleton pattern solves?

The singleton design pattern solves problems by allowing it to: Ensure that a class only has one instance. Easily access the sole instance of a class. Control its instantiation.

Are Javascript modules singletons?

If you've worked with ES6 modules, and if you didn't already know it, ES6 modules are singletons by default. Specifically, by combining modules and the const keyword, you can easily write singletons.


1 Answers

Your code's in the order you quoted it, isn't it? The singleton appears above the RequestManager in the source?

If so, that's your problem. It's fairly subtle(!), but assuming your two bits of quoted code are in the order you've shown them, here's the order in which things happen (I'll explain it more below):

  1. The function RequestManager is defined.
  2. Your anonymous function that creates your singleton runs, including instantiating an instance of RequestManager.
  3. The RequestManager prototype is replaced with a new one.

Since the myRequestManager instance was instantiated before the prototype was changed, it doesn't have the functions you defined on that (new) prototype. It continues to use the prototype object that was in place when it was instantiated.

You can fix this easily by re-ordering the code, or by adding properties to RequestManager's prototype rather than replacing it, e.g.:

RequestManager.prototype.require = function(text){
    //make an ajax request
};
RequestManager.prototype.otherFunc = function(){
    //do other things
};

That works because you haven't replaced the prototype object, you've just added to it. myRequestManager sees the additions because you've added them to the object it's using (rather that setting a new object on the constructor function's prototype property).

Why this happens is a bit technical and I'll mostly defer to the spec. When the interpreter enters a new "execution context" (e.g., a function, or the global — e.g., page-level — context), the order in which it does things is not strict top-down source order, there are phases. One of the first phases is to instantiate all of the functions defined in the context; that happens before any step-by-step code is executed. Details in all their glory in sections 10.4.1 (global code), 10.4.3 (function code) and 10.5 (declaration bindings) in the spec, but basically, the functions are created before the first line of step-by-step code. :-)

This is easiest to see with an isolated test example:

<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
<title>Test Page</title>
<style type='text/css'>
body {
    font-family: sans-serif;
}
</style>
<script type='text/javascript'>
// Uses Thing1
var User1 = (function() {
    var thing1 = new Thing1();

    function useIt() {
        alert(thing1.foo());
    }

    return useIt;
})();

// Uses Thing2
var User2 = (function() {
    var thing2 = new Thing2();

    function useIt() {
        alert(thing2.foo());
    }

    return useIt;
})();

// Thing1 gets its prototype *replaced*
function Thing1() {
    this.name = "Thing1";
}
Thing1.prototype = {
    foo: function() {
        return this.name;
    }
};

// Thing2 gets its prototype *augmented*
function Thing2() {
    this.name = "Thing2";
}
Thing2.prototype.foo = function() {
    return this.name;
};

// Set up to use them
window.onload = function() {
    document.getElementById('btnGo').onclick = go;
}

// Test!
function go() {

    alert("About to use User1");
    try
    {
        User1();
    }
    catch (e)
    {
        alert("Error with User1: " + (e.message ? e.message : String(e)));
    }

    alert("About to use User2");
    try
    {
        User2();
    }
    catch (e)
    {
        alert("Error with User2: " + (e.message ? e.message : String(e)));
    }
}

</script>
</head>
<body><div>
<div id='log'></div>
<input type='button' id='btnGo' value='Go'>
</div></body>
</html>

As you can see if you run it, User1 fails because the Thing1 instance it's using doesn't have a foo property (because the prototype was replaced), but User2 works because the Thing2 instance it uses *does (because the prototype was augmented, not replaced).

like image 191
T.J. Crowder Avatar answered Oct 18 '22 01:10

T.J. Crowder